Java中的會話管理 – HttpServlet,Cookies,URL重寫

Java中的会话管理 Servlet Web 应用程序是一个非常有趣的话题。 Java中的会话 Servlet 通过不同的方式进行管理,例如 Cookies、HttpSession API、URL 重写等。 这是 Java Web 应用程序教程系列的第三篇文章,您可能也想查看前两篇文章。

  1. Java Web 应用程序教程
  2. Java Servlet 教程

Java 中的会话管理

本文旨在通过示例程序解释 Servlet 中的会话管理使用不同的技术。

  1. 什么是会话?

  2. Java 中的会话管理 – Cookies

  3. Java Servlet 中的會話 – HttpSession

  4. Java Servlet 中的會話管理 – URL 重寫

  5. 什麼是Session?

    HTTP協議和Web服務器是無狀態的,這意味著對於Web服務器來說,每個請求都是一個新的需要處理的請求,它們無法識別是來自先前發送請求的客戶端。但有時在Web應用程序中,我們需要知道客戶端是誰,並相應地處理請求。例如,購物車應用程序應該知道是誰在發送添加商品的請求,以及商品應該添加到哪個購物車中,或者是誰在發送結帳請求,以便它可以向正確的客戶端收費。 Session 是客戶端和服務器之間的對話狀態,它可以由客戶端和服務器之間的多個請求和響應組成。由於HTTP和Web服務器都是無狀態的,維持會話的唯一方法是在每個請求和響應之間在服務器和客戶端之間傳遞有關會話的某些唯一信息(會話ID)。有幾種方法可以在請求和響應中提供唯一的標識符。

    1. 用戶身份驗證 – 這是一種非常常見的方法,用戶可以從登錄頁面提供身份驗證憑據,然後我們可以在服務器和客戶端之間傳遞身份驗證信息以維護會話。這不是非常有效的方法,因為如果同一用戶從不同的瀏覽器中登錄,它將無法工作。

    2. HTML隱藏字段 – 我們可以在HTML中創建一個唯一的隱藏字段,當用戶開始導航時,我們可以將其值設置為用戶的唯一值並跟踪會話。這種方法不能與鏈接一起使用,因為它需要在每次客戶端到服務器的請求中提交表單並携帶隱藏字段。此外,它不安全,因為我們可以從HTML源代碼中獲取隱藏字段的值並用於黑客會話。

    3. URL重寫 – 我們可以在每個請求和響應中附加一個會話標識符參數來跟踪會話。這非常繁瑣,因為我們需要在每個響應中跟踪此參數並確保它不與其他參數衝突。

    4. Cookies – Cookie是Web服務器在響應標頭中發送的一小段信息,並存儲在瀏覽器Cookie中。當客戶端發出進一步請求時,它將Cookie添加到請求標頭中,我們可以利用它來跟踪會話。我們可以使用Cookie來維持會話,但如果客戶端禁用Cookie,則它將無法工作。

    5. 會話管理API – 會話管理API是建立在上述會話跟踪方法之上的。所有上述方法的主要缺點是:

      • 大多數情況下,我們不僅想跟踪會話,還必須將一些數據存儲到會話中,以便將來的請求中使用。如果我們試圖實現這一點,這將需要大量的工作。
      • 所有上述方法都不是完全自包含的,它們中的所有方法都無法在特定情況下工作。因此,我們需要一個可以利用這些會話跟踪方法提供全面會話管理的解決方案。

      這就是為什麼我們需要 會話管理API,而J2EE Servlet技術提供了我們可以使用的會話管理API。

  6. Java中的会话管理 – Cookies

    在Web应用程序中,Cookie被广泛用于根据您的选择个性化响应或跟踪会话。在进入Servlet会话管理API之前,我想展示如何通过一个小型Web应用程序使用Cookie来跟踪会话。我们将创建一个名为ServletCookieExample的动态Web应用程序,项目结构如下图所示。Web应用程序的部署描述符web.xml如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://java.sun.com/xml/ns/javaee" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
      <display-name>ServletCookieExample</display-name>
      <welcome-file-list>
        <welcome-file>login.html</welcome-file>
      </welcome-file-list>
    </web-app>
    

    我们应用程序的欢迎页面是login.html,在这里我们将从用户那里获取身份验证详细信息。

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="US-ASCII">
    <title>登录页面</title>
    </head>
    <body>
    
    <form action="LoginServlet" method="post">
    
    用户名: <input type="text" name="user">
    <br>
    密码: <input type="password" name="pwd">
    <br>
    <input type="submit" value="登录">
    </form>
    </body>
    </html>
    

    这是负责处理登录请求的LoginServlet。

    package com.journaldev.servlet.session;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * Servlet implementation class LoginServlet
     */
    @WebServlet("/LoginServlet")
    public class LoginServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    	private final String userID = "Pankaj";
    	private final String password = "journaldev";
    
    	protected void doPost(HttpServletRequest request,
    			HttpServletResponse response) throws ServletException, IOException {
    
    		// 获取userID和密码的请求参数
    		String user = request.getParameter("user");
    		String pwd = request.getParameter("pwd");
    		
    		if(userID.equals(user) && password.equals(pwd)){
    			Cookie loginCookie = new Cookie("user",user);
    			// 将Cookie设置为30分钟后过期
    			loginCookie.setMaxAge(30*60);
    			response.addCookie(loginCookie);
    			response.sendRedirect("LoginSuccess.jsp");
    		}else{
    			RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
    			PrintWriter out= response.getWriter();
    			out.println("<font color=red>用户名或密码错误。</font>");
    			rd.include(request, response);
    		}
    
    	}
    
    }
    

    注意我们设置的Cookie,然后将其转发到LoginSuccess.jsp,在那里将使用该Cookie来跟踪会话。还请注意,Cookie的超时设置为30分钟。理想情况下,应该有复杂的逻辑来设置用于会话跟踪的Cookie值,以便不会与任何其他请求发生冲突。

    <%@ page language="java" contentType="text/html; charset=US-ASCII"
        pageEncoding="US-ASCII"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
    <title>登录成功页面</title>
    </head>
    <body>
    <%
    String userName = null;
    Cookie[] cookies = request.getCookies();
    if(cookies !=null){
    for(Cookie cookie : cookies){
    	if(cookie.getName().equals("user")) userName = cookie.getValue();
    }
    }
    if(userName == null) response.sendRedirect("login.html");
    %>
    <h3>你好,<%=userName %>,登录成功。</h3>
    <br>
    <form action="LogoutServlet" method="post">
    <input type="submit" value="登出" >
    </form>
    </body>
    </html>
    

    请注意,如果我们尝试直接访问JSP页面,它将把我们转发到登录页面。当我们点击登出按钮时,我们应确保Cookie从客户端浏览器中删除。

    package com.journaldev.servlet.session;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * Servlet implementation class LogoutServlet
     */
    @WebServlet("/LogoutServlet")
    public class LogoutServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
           
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        	response.setContentType("text/html");
        	Cookie loginCookie = null;
        	Cookie[] cookies = request.getCookies();
        	if(cookies != null){
        	for(Cookie cookie : cookies){
        		if(cookie.getName().equals("user")){
        			loginCookie = cookie;
        			break;
        		}
        	}
        	}
        	if(loginCookie != null){
        		loginCookie.setMaxAge(0);
            	response.addCookie(loginCookie);
        	}
        	response.sendRedirect("login.html");
        }
    
    }
    

    没有方法可以删除Cookie,但我们可以将最大年龄设置为0,以便立

  7. Java Servlet 中的 Session – HttpSession

    Servlet API 通过 HttpSession 接口提供了会话管理。我们可以使用以下方法从 HttpServletRequest 对象中获取会话。HttpSession 允许我们将对象设置为属性,这些属性可以在将来的请求中检索。

    1. HttpSession getSession() – 此方法始终返回一个 HttpSession 对象。如果请求附带了会话对象,则返回附加的会话对象;如果请求没有附带会话,则创建一个新的会话并返回。
    2. HttpSession getSession(boolean flag) – 如果请求有会话,则此方法返回 HttpSession 对象;否则返回 null。

    HttpSession 的一些重要方法包括:

    1. String getId() – 返回包含分配给此会话的唯一标识符的字符串。
    2. Object getAttribute(String name) – 返回绑定到会话中指定名称的对象,如果没有绑定对象,则返回 null。一些其他处理会话属性的方法包括 getAttributeNames()removeAttribute(String name)setAttribute(String name, Object value)
    3. long getCreationTime() – 返回会话创建的时间,以自1970年1月1日格林威治时间午夜以来的毫秒数表示。我们可以使用 getLastAccessedTime() 方法获取最后访问的时间。
    4. setMaxInactiveInterval(int interval) – 指定在 Servlet 容器在使此会话无效之前,客户端请求之间的时间间隔(以秒为单位)。我们可以使用 getMaxInactiveInterval() 方法获取会话超时值。
    5. ServletContext getServletContext() – 返回应用程序的 ServletContext 对象。
    6. boolean isNew() – 如果客户端尚未知道会话,或者客户端选择不加入会话,则返回 true。
    7. void invalidate() – 使此会话无效,然后解除绑定到它的任何对象。

    当我们使用 HttpServletRequest 的 getSession() 方法并且它创建一个新请求时,它会创建新的 HttpSession 对象,并且还会向响应对象添加一个名为 JSESSIONID、值为会话ID的 Cookie。此 Cookie 用于在客户端的后续请求中识别 HttpSession 对象。如果客户端禁用了 Cookie 并且我们使用 URL 重写,则此方法使用请求 URL 中的 jsessionid 值来查找相应的会话。JSESSIONID Cookie 用于会话跟踪,因此我们不应该将其用于我们应用程序的目的,以避免任何与会话相关的问题。让我们看一下使用 HttpSession 对象进行会话管理的示例。我们将在 Eclipse 中创建一个动态 Web 项目,使用 Servlet 上下文作为 ServletHttpSessionExample。项目结构将如下图所示。 login.html 与以前的示例相同,并在 web.xml 中定义为应用程序的欢迎页面。LoginServlet servlet 将创建会话并设置我们可以在其他资源或将来的请求中使用的属性。

    package com.journaldev.servlet.session;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * Servlet implementation class LoginServlet
     */
    @WebServlet("/LoginServlet")
    public class LoginServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    	private final String userID = "admin";
    	private final String password = "password";
    
    	protected void doPost(HttpServletRequest request,
    			HttpServletResponse response) throws ServletException, IOException {
    
    		// get request parameters for userID and password
    		String user = request.getParameter("user");
    		String pwd = request.getParameter("pwd");
    		
    		if(userID.equals(user) && password.equals(pwd)){
    			HttpSession session = request.getSession();
    			session.setAttribute("user", "Pankaj");
    			//setting session to expiry in 30 mins
    			session.setMaxInactiveInterval(30*60);
    			Cookie userName = new Cookie("user", user);
    			userName.setMaxAge(30*60);
    			response.addCookie(userName);
    			response.sendRedirect("LoginSuccess.jsp");
    		}else{
    			RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
    			PrintWriter out= response.getWriter();
    			out.println("<font color=red>Either user name or password is wrong.</font>");
    			rd.include(request, response);
    		}
    
    	}
    
    }
    

    我们的 LoginSuccess.jsp 代码如下。

    <%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
    <title>Login Success Page</title>
    </head>
    <body>
    <%
    //allow access only if session exists
    String user = null;
    if(session.getAttribute("user") == null){
    response.sendRedirect("login.html");
    }else user = (String) session.getAttribute("user");
    String userName = null;
    String sessionID = null;
    Cookie[] cookies = request.getCookies();
    if

  8. Java Servlet 中的会话管理 – URL 重写

    正如我们在上一节中所看到的,我们可以使用 HttpSession 来管理会话,但如果在浏览器中禁用了 cookie,它将无法工作,因为服务器将无法从客户端接收到 JSESSIONID cookie。Servlet API 提供了对 URL 重写的支持,我们可以使用它来管理这种情况下的会话。最好的部分是,从编码的角度来看,使用起来非常简单,只涉及一步 – 对 URL 进行编码。Servlet URL 编码的另一个好处是,它是一种后备方法,只有在浏览器禁用 cookie 的情况下才会启用。我们可以使用 HttpServletResponse 的 encodeURL() 方法对 URL 进行编码,如果我们必须将请求重定向到另一个资源并且想要提供会话信息,我们可以使用 encodeRedirectURL() 方法。我们将创建一个类似于上面的项目,只是我们将使用 URL 重写方法来确保即使在浏览器中禁用了 cookie,会话管理也能正常工作。Eclipse 中 ServletSessionURLRewriting 项目的结构如下图所示。

    package com.journaldev.servlet.session;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * Servlet implementation class LoginServlet
     */
    @WebServlet("/LoginServlet")
    public class LoginServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    	private final String userID = "admin";
    	private final String password = "password";
    
    	protected void doPost(HttpServletRequest request,
    			HttpServletResponse response) throws ServletException, IOException {
    
    		// get request parameters for userID and password
    		String user = request.getParameter("user");
    		String pwd = request.getParameter("pwd");
    		
    		if(userID.equals(user) && password.equals(pwd)){
    			HttpSession session = request.getSession();
    			session.setAttribute("user", "Pankaj");
    			//setting session to expiry in 30 mins
    			session.setMaxInactiveInterval(30*60);
    			Cookie userName = new Cookie("user", user);
    			response.addCookie(userName);
    			//Get the encoded URL string
    			String encodedURL = response.encodeRedirectURL("LoginSuccess.jsp");
    			response.sendRedirect(encodedURL);
    		}else{
    			RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
    			PrintWriter out= response.getWriter();
    			out.println("<font color=red>Either user name or password is wrong.</font>");
    			rd.include(request, response);
    		}
    
    	}
    
    }
    
    <%@ page language="java" contentType="text/html; charset=US-ASCII"
        pageEncoding="US-ASCII"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
    <title>登陆成功页面</title>
    </head>
    <body>
    <%
    //allow access only if session exists
    String user = null;
    if(session.getAttribute("user") == null){
    	response.sendRedirect("login.html");
    }else user = (String) session.getAttribute("user");
    String userName = null;
    String sessionID = null;
    Cookie[] cookies = request.getCookies();
    if(cookies !=null){
    for(Cookie cookie : cookies){
    	if(cookie.getName().equals("user")) userName = cookie.getValue();
    	if(cookie.getName().equals("JSESSIONID")) sessionID = cookie.getValue();
    }
    }else{
    	sessionID = session.getId();
    }
    %>
    <h3>您好,<%=userName %>,登陆成功。您的会话 ID=<%=sessionID %></h3>
    <br>
    用户=<%=user %>
    <br>
    <!-- 需要对所有希望传递会话信息的 URL 进行编码 -->
    <a href="<%=response.encodeURL("CheckoutPage.jsp") %>">结账页面</a>
    <form action="<%=response.encodeURL("LogoutServlet") %>" method="post">
    <input type="submit" value="注销" >
    </form>
    </body>
    </html>
    
    <%@ page language="java" contentType="text/html; charset=US-ASCII"
        pageEncoding="US-ASCII"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
    <title>登陆成功页面</title>
    </head>
    <body>
    <%
    String userName = null;
    //allow access only if session exists
    if(session.getAttribute("user") == null){
    	response.sendRedirect("login.html");
    }else userName = (String) session.getAttribute("user");
    String sessionID = null;
    Cookie[] cookies = request.getCookies();
    if(cookies !=null){
    for(Cookie cookie : cookies){
    	if(cookie.getName().equals("user")) userName = cookie.getValue();
    }
    }
    %>
    <h3>您好,<%=userName %>,请结账。</h3>
    <br>
    <form action="<%=response.encodeURL("LogoutServlet") %>" method="post">
    <input type="submit" value="注销" >
    </form>
    </body>
    </html>
    

    package com.journaldev.servlet.session;

    import java.io.IOException;

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;

    /**
    * Servlet implementation class LogoutServlet
    */
    @WebServlet("/LogoutServlet")
    public class Logout

這就是java servlets中的會話管理的全部,我們將在未來的文章中研究Servlet Filters、Listeners和Cookies。更新:查看系列中的下一篇文章Servlet Filter

下載項目

Source:
https://www.digitalocean.com/community/tutorials/java-session-management-servlet-httpsession-url-rewriting