Java中的会话管理 Servlet Web应用程序是一个非常有趣的话题。Java中的会话 Servlet是通过不同的方式进行管理的,例如Cookies、HttpSession API、URL重写等。这是Java Web应用程序教程系列的第三篇文章,您可能也想查看前两篇文章。
Java中的会话管理
本文旨在使用不同的技术和示例程序解释Servlet中的会话管理。
-
会话是什么?
HTTP 协议和 Web 服务器是无状态的,这意味着对于 Web 服务器来说,每个请求都是一个新的要处理的请求,它们无法识别是否来自先前发送请求的客户端。但有时在 Web 应用程序中,我们需要知道客户端是谁,并相应地处理请求。例如,购物车应用程序应该知道谁正在发送添加商品的请求,以及商品应该添加到哪个购物车,或者谁正在发送结账请求,以便可以向正确的客户收费。 会话 是客户端和服务器之间的一种交互状态,它可以由客户端和服务器之间的多个请求和响应组成。由于 HTTP 和 Web 服务器都是无状态的,因此在每个请求和响应中传递有关会话的一些唯一信息(会话 ID)是维护会话的唯一方法。有几种方法可以在请求和响应中提供唯一标识符。
-
用户身份验证 – 这是一种非常常见的方式,用户可以从登录页面提供身份验证凭据,然后我们可以在服务器和客户端之间传递身份验证信息以维护会话。这不是非常有效的方法,因为如果同一用户从不同的浏览器登录,它就无法工作。
-
HTML 隐藏字段 – 我们可以在 HTML 中创建一个唯一的隐藏字段,当用户开始导航时,我们可以将其值设置为唯一的用户,并跟踪会话。这种方法不能与链接一起使用,因为它需要在每次从客户端到服务器的请求中提交表单并携带隐藏字段。此外,它不安全,因为我们可以从 HTML 源代码中获取隐藏字段值并使用它来入侵会话。
-
URL 重写 – 我们可以在每个请求和响应中附加一个会话标识符参数来跟踪会话。这非常繁琐,因为我们需要在每个响应中跟踪此参数,并确保它不会与其他参数冲突。
-
Cookie – Cookie 是 Web 服务器在响应标头中发送的小段信息,并存储在浏览器的 Cookie 中。当客户端发出进一步的请求时,它会将 Cookie 添加到请求标头中,我们可以利用它来跟踪会话。我们可以使用 Cookie 维护会话,但是如果客户端禁用了 Cookie,则它将无法工作。
-
会话管理 API – 会话管理 API 是建立在上述会话跟踪方法之上的。所有上述方法的主要缺点是:
- 大多数情况下,我们不只是想跟踪会话,还必须将一些数据存储到会话中,以便我们可以在未来的请求中使用。如果我们试图实现这一点,将需要付出很大的努力。
- 所有上述方法都不是完全自给自足的,它们中的所有方法都无法在特定情况下工作。因此,我们需要一种解决方案,可以利用这些会话跟踪方法来提供所有情况下的会话管理。
这就是为什么我们需要 会话管理 API,而 J2EE Servlet 技术提供了我们可以使用的会话管理 API。
-
-
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 { // get request parameters for userID and password String user = request.getParameter("user"); String pwd = request.getParameter("pwd"); if(userID.equals(user) && password.equals(pwd)){ Cookie loginCookie = new Cookie("user",user); //setting cookie to expiry in 30 mins 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,以便它立即从客户端浏
-
Java Servlet中的会话 – HttpSession
Servlet API通过
HttpSession
接口提供会话管理。我们可以使用以下方法从HttpServletRequest对象中获取会话。HttpSession允许我们将对象设置为属性,这些属性可以在将来的请求中检索。- HttpSession getSession() – 此方法始终返回一个HttpSession对象。如果请求附带了会话对象,则返回该会话对象,如果请求没有附带会话,则创建一个新会话并返回。
- HttpSession getSession(boolean flag) – 如果请求具有会话,则返回HttpSession对象,否则返回null。
HttpSession的一些重要方法包括:
- String getId() – 返回包含分配给此会话的唯一标识符的字符串。
- Object getAttribute(String name) – 返回绑定在会话中指定名称下的对象,如果没有绑定任何对象,则返回null。一些其他处理会话属性的方法包括
getAttributeNames()
、removeAttribute(String name)
和setAttribute(String name, Object value)
。 - long getCreationTime() – 返回会话创建的时间,以自1970年1月1日格林尼治时间午夜起的毫秒数为单位。我们可以使用
getLastAccessedTime()
方法获取上次访问时间。 - setMaxInactiveInterval(int interval) – 指定在servlet容器在无法接收到客户端请求之前使此会话无效之间的时间,以秒为单位。我们可以使用
getMaxInactiveInterval()
方法获取会话超时值。 - ServletContext getServletContext() – 返回应用程序的ServletContext对象。
- boolean isNew() – 如果客户端尚未了解会话或者客户端选择不加入会话,则返回true。
- void invalidate() – 使此会话失效,然后取消绑定到它的任何对象。
理解JSESSIONID Cookie
当我们使用HttpServletRequest的getSession()方法并且它创建一个新请求时,它会创建新的HttpSession对象,并且还会在响应对象中添加一个名为JSESSIONID的Cookie,其值为会话ID。此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(cookies !=null){
for(Cookie cookie : cookies){
if(cookie.getName -
Java Servlet中的会话管理 – URL重写
正如我们在上一节中看到的,我们可以使用HttpSession来管理会话,但是如果在浏览器中禁用了cookies,它将无法工作,因为服务器将无法从客户端接收到JSESSIONID cookie。Servlet API提供了对URL重写的支持,我们可以利用它来在这种情况下管理会话。最好的部分是,从编码的角度来看,使用起来非常简单,只涉及一步 – 对URL进行编码。Servlet URL编码的另一个好处是,它是一种后备方法,只有在浏览器cookies被禁用时才会启用。我们可以使用HttpServletResponse的
encodeURL()
方法来对URL进行编码,如果我们必须将请求重定向到另一个资源并且我们想要提供会话信息,我们可以使用encodeRedirectURL()
方法。我们将创建一个类似上面的项目,只是我们将使用URL重写方法来确保即使在浏览器中禁用了cookies,会话管理也能正常工作。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> <% //仅当会话存在时才允许访问 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; //仅当会话存在时才允许访问 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 LogoutServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); Cookie[] cookies = request.getCookies(); if(cookies != null){ for(Cookie cookie : cookies){ if(cookie.getName().equals("JSESSIONID")){ System.out.println("JSESSIONID="+cookie.getValue()); } cookie.setMaxAge(0); response.addCookie(cookie); } } //invalidate the session if exists HttpSession session = request.getSession(false); System.out.println("User="+session.getAttribute("user")); if(session != null){ session.invalidate(); } //no encoding because we have invalidated the session response.sendRedirect("login.html"); } }
当我们在浏览器中禁用cookies并运行此项目时,下图显示了响应页面,注意浏览器地址栏中的jsessionid。还请注意,在LoginSuccess页面上,用户名为null,因为浏览器没有发送上次响应中发送的cookie。
如果没有禁用cookies,您将在URL中看不到jsessionid,因为在那种情况下,Servlet会话API将使用cookies。
这就是关于 Java Servlet 中会话管理的全部内容,我们将在未来的文章中查看 Servlet 过滤器、监听器和 Cookie。更新:查看系列中的下一篇文章Servlet 过滤器。