JSP&Servlet学习笔记
会话管理
会话管理基本原理
- Web应用程序的请求与响应是基于HTTP,为无状态的通信协议,服务器不会“记得”这次请求与下次请求的关系。然而有些功能是必须由多次请求来完成,就比如用户在多个购物网页中添加至购物车,Web应用程序必须有个方式来得知用户在这些网页中采购了哪些商品,这种记得此次请求与之后请求间关系的方式,就成为会话管理。
- 本节将介绍几个实现会话管理的基本方式,如隐藏域、Cookie与URL重写(URL Rewriting)的实现方式,了解这些基本会话管理的实现方式,有助于了解下一节HttpSession的使用方式与原理。
使用隐藏域
- 隐藏域时主动告知服务器多次请求间必要信息的方式之一。以问卷作答为例,上一页的问卷答案,可以用隐藏域的方式放在下一页的窗体中,这样发送下一页窗体时,就可以一并发送这些隐藏域,每一页的问卷答案就可以保留下来。
- 将上一次的结果变成下一页的隐藏域做法之一是将上一页的结果发送至服务器,后由服务器将上一页的结果以隐藏域的方式再响应给服务器。
- 以下这个范例是个简单的示范,程序有两页问卷,第一页的结果会在第二页成为隐藏域,当第二页发送之后,可以看到两页问卷的所有答案。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/questionnaire")
public class Questionnaire extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01" + " Transitional//EN'>");
out.println("<html>");
out.println("<title>Questionnaire</title>");
out.println("</head>");
out.println("<body>");
String page = request.getParameter("page");//page请求参数决定显示哪一页问卷
out.println("<form action='questionnaire' method='post'>");
if(page == null)
{
out.println("问题一:<input type='text' name='p1q1'><br>");
out.println("问题二:<input type='text' name='p1q2'><br>");
out.println("<input type='submit' name='page' value='下一页'>");
}
else if("下一页".equals(page))
{
String p1q1 = request.getParameter("p1q1");
String p1q2 = request.getParameter("p1q2");
out.println("问题三:<input type='text' name='p2q1'><br>");
out.println("<input type='hidden' name='p1q1' value='" + p1q1 +"'>");
out.println("<input type='hidden' name='p1q2' value='" + p1q2 +"'>");
out.println("<input type='submit' name='page' value='完成'>");
}
else if("完成".equals(page))
{
out.println(request.getParameter("p1q1")+"<br>");
out.println(request.getParameter("p1q2")+"<br>");
out.println(request.getParameter("p2q1")+"<br>");
}
out.println("</form>");
out.println("</body>");
out.println("</html>");
out.close();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
}
- 由于程序只使用一个Servlet,所以利用一个page请求参数来区别该显示第几页问卷。没有发送page请求参数时显示第一页。
- 使用隐藏域的方式,再关掉网页之后,显然会遗失先前请求的信息,所以仅适合用于一些简单地状态处理,如在线问卷。由于在查看网页源代码时,就可以看到隐藏域的值,因此这个方法不适用于隐秘性较高的数据。
- 隐藏域不是Servlet、JSP实际管理会话时的机制,在这边实现隐藏域,只是为了说明,有浏览器主动告知必要的信息,为实现Web应用程序会话管理的基本原理。
使用Cookie
- Web应用程序会话管理的基本方式,就是在此次请求中,将下一次请求时的服务器应知道的信息,先响应给服务器,有浏览器在之后的请求再一并发送给应用程序,这样应用程序就可以“得知”多次请求的相关数据。
- Cookie是在浏览器存储信息的一种方式,服务器可以相应浏览器set-cookie标头,浏览器收到标头和数值后,会将它以文件的形式存储在计算机上,这个文件就称之为Cookie。可以设定给Cookie一个存活期限,保留一些有用的信息在客户端,如果关闭浏览器后,再次打开浏览器并连接服务器时,这些Cookie仍在有效期限中,浏览器会使用cookie标头自动将Cookie发送给服务器,服务器就可以得知一些先前浏览器请求的相关信息。
- Cookie可以设定存活期限,所以自爱客户端存储信息可以活的更久一些(但是用户可以主动清除Cookie信息)。有些购物网站会使用Cookie来记录用户的浏览时间,虽然用户没有实际购买商品,但是在下一次用户访问时,仍可以根据Cookie中保持的浏览历史记录为用户建议购物清单。
- Servlet本身提供了创建、设置与读取Cookie的API。如果要创建Cookie,可以使用Cookie类,创建时指定Cookie中的名称与数值,并使用HttpServletResponse的addCookie()方法在响应中新增Cookie。如:
Cookie cookie = new Cookie("user", "lancibe");
cookie.setMaxAge(7 * 24 * 60 * 60);//单位是秒
response.addCookie(cookie);
HTTP中Cookie的设定是通过set-cookie标头,所以必须在实际响应浏览器之前使用addCookie()来新增Cookie实例,在浏览器输出HTML响应之后再运行addCookie()是没有作用的。
- 如范例中所示,创建Cookie之后,就可以使用setMaxAge()设定Cookie的有效期限,设定单位是秒。默认关闭浏览器后就失效。
- 如果要取得浏览器上存储的Cookie,则可以从HttpServletRequest的getCookies()来取得,这可取得属于该网页所有域的(Domain)的所有Cookie,返回值为Cookie[]数组。取得Cookie对象后,可以使用Cookie的getName()与getValue()方法,分别取得Cookie的名称与数值。例如:
Cookie[] cookies = request.getCookies();
if(cookies != null)
{
for(Cookie cookie : cookies)
{
String name = cookie.getName();
String value = cookie.getValue();
...
}
}
- Cookie上另一个常见的应用,就是实现用户自动登录(Login)功能。在用户登录窗体上,可以经常看到有个自动登录的选项,登录时若有选取该选项,下次在登录该网站时,就不用再输入名称密码。
- 以下将实现一个简单的范例来示范Cookie API的使用。当用户访问首页时,会检查用户先前是否允许自动登录,如果是的话,就直接将转送至用户界面。
package chapter4;
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 java.io.IOException;
@WebServlet("/CookieDemoIndex.do")
public class Index extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Cookie[] cookies = request.getCookies();
if(cookies != null)
{
for(Cookie cookie : cookies)
{
String name = cookie.getName();
String value = cookie.getValue();
if("user".equals(name) && "lancibe".equals(value))
{
request.setAttribute(name, value);
request.getRequestDispatcher("/CookieDemoUser.view").forward(request, response);
return;
}
}
}
response.sendRedirect("CookieDemoLogin.html");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
}
- 当用户访问首页时,回先取得所有Cookie,然后一个一个检查是否有Cookie存储名称user且值为lancibe,如果有,则表示先前用户登录时,增精选去了“自动登录”选项,因此直接转发至用户网页。如果没有对应的Cookie,则表示用户是初次造访,或者先前没有选取“自动登录”选项,此时重定向到登录窗体。
- 登录窗体会发送负责处理登录请求的Servlet,其实现程序代码如下:
package chapter4;
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 java.io.IOException;
@WebServlet("/CookieDemoLogin.do")
public class Login extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = req.getParameter("user");
String passwd = req.getParameter("passwd");
if("lancibe".equals(user) && "123456".equals(passwd))
{
String login = req.getParameter("login");
if("auto".equals(login))
{
Cookie cookie = new Cookie("user", "lancibe");
cookie.setMaxAge(7*24*60*60);
resp.addCookie(cookie);
}
req.setAttribute("user", user);
req.getRequestDispatcher("CookieDemoUser.view").forward(req,resp);
}
else
{
resp.sendRedirect("CookieDemoLogin.html");
}
}
}
- 当登录名称与密码正确时,如果用户有选取“自动登录”选项,请求中会带有login参数且值为auto,一旦检查到有这个请求参数,创建Cookie实例、设定Cookie有效期限并加入响应之中。下次用户在请求刚才示范的Index程序时,就可以取得对应的Cookie值,因此就可以实现自动登录的流程。
- 用户名与密码正确的话,还会在请求属性中设定user属性,而后转发至用户界面,否则,要求浏览器重定向到登录界面。接下来是用户界面的实现。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/CookieDemoUser.view")
public class User extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException
{
response.setContentType("text/html;charset=UTF-8");
if(request.getAttribute("user") == null)
{
response.sendRedirect("CookieDemoLogin.html");
}
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01" + " Transitional//EN'>");
out.println("<html>");
out.println("<title>Servlet User</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>" +
request.getAttribute("user") + "已登录</h1>");
out.println("</body>");
out.println("</html>");
out.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
}
-
在这个范例中,无论是通过窗体登录,或使用Cookie自动登录,请求中一定会带有User属性,此时显示欢迎用户的信息,否则就重定向至登录网页。
-
在Servlet3.0中,Cookie类新增了setHtttpOnly()方法,可以见Cookie标示为仅用于HTTP,这会在set-cookie标头上附加HttpOnly属性,在浏览器支持的情况下,这个Cookie将不会被客户端脚本(例如JavaScript)读取,可以使用isHttpOnly()来得知一个Cookie是否被标记。
使用URL重写
- 所谓URL重写(URL Rewriting),其实就是GET请求参数的应用,当服务器响应浏览器上一次请求时,将某些相关信息以超链接的方式响应给服务器,超链接中包括请求参数信息。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/UrlRwDemoSearch")
public class Search extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<!DOCTYPE HTML>");
out.println("<html>");
out.println("<title>搜索结果</title>");
out.println("</head>");
out.println("<body>");
String start = req.getParameter("start");
if(start == null)
{
start = "1";
}
int count = Integer.parseInt(start);
int begin = 10 * count - 9;
int end = 10 * count;
out.println("第" + begin + "到" + end + "搜索结果<br>");
out.println("<ul>");
for(int i = 1 ; i <= 10 ; i++)
{
out.println("<li>搜寻结果"+ i + "</li>");
}
out.println("</ul>");
for(int i = 1 ; i <= 10 ; i++)
{
if(i ==count)
{
out.println(i);
continue;
}
out.println("<a href='UrlRwDemoSearch?start=" +
i + "'>" + i + "</a>");
}
out.println("</body>");
out.println("</html>");
out.close();
}
}
- 显然,因为URL重写是在超链接之后附加信息的方式,所以必须以GET方式发送请求,再加上GET本身可以携带的请求参数长度有限,因此大量的客户端信息保留,并不适合使用URL重写。
- 通常URL重写是用在一些简单的客户端信息保留,或者是辅助会话管理。
HttpSession会话管理
- 前面介绍了三个会话管理的基本方式,无论哪个方式,都必须自行处理对浏览器的响应,决定哪些信息必须发送至浏览器,以便在之后的请求中一并发送相关信息,供Web应用程序辨识请求间的关联。
- 这一节将介绍Servlet/JSP中进行会话管理的机制:使用HttpSession。会看到HttpSession的基本API使用方式,以及其会话管理的背后原理。可以将回话期间必须共享的数据,保存在HttpSession中成为属性。也会看到,即使用户关闭浏览器接收Cookie的功能,HttpSession也可以改用URL重写继续其会话管理功能。
使用HttpSession
- 在Servlet/JSP中,如果想要进行会话管理,可以使用HttpServletRequest的**getSession()**方法取得HttpSession对象
HttpSession session = requst.getSession();
- 该方法有两个版本,另一个版本可以传入布尔值,默认是true,表示若尚未存在HttpSession实例时,直接创建一个新的对象。若传入false,若尚未存在HttpSession实例,则直接返回null。
- HttpSession上最常用的方法大概就是setAttribute()和getAttribute(),从名称上应该可以猜得到,他们两个与HttpServletRequest的setAttribute()和getAttribute()类似,可以让我们在对象中设置及取得属性,这是可以存放属性对象的第二个地方。
- 如果想在浏览器与Web应用程序的回话期间,保留请求之间的相关信息,可以使用HttpSession的setAttribute()方法将相关信息设置为属性。在会话期间,就可以当做Web应用程序“记得”客户端的信息,如果想取出这些信息,则通过HttpSession的getAttribute()就可以。
- 以下范例是将前面的在线问卷,从隐藏域方式改用HttpSession方式来实现会话管理。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/questionnaire")
public class Questionnaire extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01" + " Transitional//EN'>");
out.println("<html>");
out.println("<title>Questionnaire</title>");
out.println("</head>");
out.println("<body>");
String page = request.getParameter("page");//page请求参数决定显示哪一页问卷
out.println("<form action='questionnaire' method='post'>");
if(page == null)
{
out.println("问题一:<input type='text' name='p1q1'><br>");
out.println("问题二:<input type='text' name='p1q2'><br>");
out.println("<input type='submit' name='page' value='下一页'>");
}
else if("下一页".equals(page))
{
String p1q1 = request.getParameter("p1q1");
String p1q2 = request.getParameter("p1q2");
HttpSession session = request.getSession();
session.setAttribute("p1q1", p1q1);
session.setAttribute("p1q2", p1q2);
out.println("问题三:<input type='text' name='p2q1'><br>");
out.println("<input type='submit' name='page' value='完成'>");
}
else if("完成".equals(page))
{
HttpSession session = request.getSession();
session.getAttribute("p1q1");
out.println(session.getAttribute("p1q1")+"<br>");
out.println(session.getAttribute("p1q2")+"<br>");
out.println(request.getParameter("p2q1")+"<br>");
}
out.println("</form>");
out.println("</body>");
out.println("</html>");
out.close();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
}
- 默认在管理浏览器之前,取得HttpSession都是相同的实例。若果想在此次回话期间,直接让目前的HttpSession失效,可以执行HttpSession的invalidate()方法。一个使用的时机就是实现注销机制,如以下范例所示,首先是登录的Servlet实现。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/HttpSessionDemoLogin.do")
public class Login2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = req.getParameter("user");
String passwd = req.getParameter("passwd");
if("lancibe".equals(user) && "123456".equals(passwd))
{
req.getSession().setAttribute("login", user);
req.getRequestDispatcher("/HttpSessionDemoUser.view").forward(req, resp);
}
else
{
resp.sendRedirect("Login.html");
}
}
}
- 在登录时,如果名称、密码正确,就取得HttpSession并设定一个login属性,用于代表用户做完成登录的动作。其他的Servlet/JSP,如果可以从HttpSession取得login属性,基本上就可以确定是个已登录用户,这类用来识别用户是否登录的属性,通常称之为登录令牌(Login Token)。上面这个范例在登陆成功之后,会转发至用户页面。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/HttpSessionDemoUser.view")
public class User2 extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
HttpSession session = request.getSession();
if (session.getAttribute("login") == null)
{
response.sendRedirect("login.html");
}
else
{
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC>");
out.println("<html>");
out.println("<title>欢迎"+ session.getAttribute("login") +"</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>用户 "+ session.getAttribute("login") + "已登录</h1><br>");
out.println("<br><a href='HttpSessionDemoLogout.view'>注销</a>");//执行注销的超链接
out.println("</body>");
out.println("</html>");
out.close();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
processRequest(req, resp);
}
}
- 如果有浏览器请求用户界面,程序先尝试取得HttpSession中的login属性,如果无法取得,表示用户尚未登录,要求浏览器重定向到登录窗体。如果可以取得login属性,显示用户界面,页面中有个可以执行注销的URL超链接,按下后会执行以下程序。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/HttpSessionDemoLogout.view")
public class Logout extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
HttpSession session = req.getSession();
String user = (String) session.getAttribute("login");
session.invalidate();//使HttpSession失效
PrintWriter out = resp.getWriter();
out.println("<!DOCTYPE HTML>");
out.println("<html>");
out.println("<title>注销</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>用户 "+ user + "已注销</h1><br>");
out.println("</body>");
out.println("</html>");
out.close();
}
}
- 执行HttpSession的invalidate()之后,容器就会销毁回收HttpSession对象,如果再次通过HttpServletRequest的getSession(),取得HttpSession就是另一个新对象了,这个新对象当然不会有先前的login属性,所以在直接请求用户界面,就会因找不到login属性而重定向至登录窗体。
HttpSession并非线程安全,所以必须注意属性设定时共享存取的问题。
HttpSession会话管理原理
- 使用HttpSession进行会话管理十分方便,让Web应用程序看似可以“记得”浏览器发出的请求,连接数个请求之间的关系。但无论如何,Web应用程序基于HTTP协议的事实并没有改变,实际上如何得知数个请求之间的关系,这种工作是由Web容器执行。
- 尝试运行HttpServletRequest的getSessino()时,Web容器会创建HttpSession对象,关键在于每个HttpSession对象都会有个特殊的ID,称为Session ID,可以执行HttpSession的getId()来取得Session ID。这个Session ID默认会使用Cookie存放在浏览器中。在Tomcat中,Cookie的名称是JSESSIONID,数值则是getId()所取得的Session ID。
- 由于Web容器本身是执行于JVM中的一个Java程序,通过getSession()取得HttpSession,是Web容器中的一个Java对象,HttpSession中存放的属性,自然也就存放于服务器端的Web容器中。每一个HttpSession各有特殊的Session ID,当浏览器请求应用程序时,会将Cookie中存放的Session ID一并发送给应用程序,Web容器会根据Session ID来找出对应的HttpSession对象,这样就可以取得个浏览器个别的会话数据。
- 所以使用HttpSession来进行会话管理时,设定为属性的对象是存储在服务器端,而Session ID默认使用Cookie存放于浏览器端。Web容器存储Session ID的Cookie“默认”为关闭浏览器就失效,所以重新启动浏览器请求应用程序时,通过getSession()取得的都是新的HttpSession对象。
- 每次请求来到应用程序时,容器会根据发送过来的Session ID取得对应的HttpSession。由于HttpSession对象会占用内存空间,所以HttpSession的属性中尽量不要存储耗资源的大型对象,必要时将属性移除,或者不需要使用HttpSession时,执行invalidate()让HttpSession失效。
- 可以执行HttpSession的setMaxInactiveInterval()方法,设定浏览器多久没有请求应用程序的话,HttpSession就自动失效,设定的单位是**“秒”。也可以在web.xml中设定HttpSession默认的失效时间,但要特别注意,此处设定的时间单位是“分钟”**。
</web-app...>
...
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
- 在Servlet3.0中新增了SessionCookieConfig接口,可以通过ServletContext的getSessionCookieConfig()来取得实现该接口的对象,ServletContext可以通过Servlet实例的getServletContext()来取得。通过SessionCookieConfig实现对象,可以设定存储Session ID的Cookie相关信息,例如可以通过setName()将默认的Session ID名称修改为别的名称,通过setAge()设定存储Session ID的Cookie存活期限等,单位是“秒”。
- 但是要注意的是,设定SessionCookieConfig必须在ServletContext初始化之前,所以实际上要修改Session ID、存储Session ID的Cookie存活期限等信息时,必须在web.xml中设定。例如:
</web-app...>
...
<sessino-config>
<session-timeout>30</session-timeout>
<cookie-config>
<name>sid-lancibe</name>
<http-only>true</http-only>
</cookie-config>
</sessino-config>
</web-app>
- 另一个方法是实现ServletContextListener,容器在初始化ServletContext时会调用ServletContextListener的contextInitialized()方法,可以在其中取得ServletContext进行SessionCookieConfig设定。
HttpSession与URL重写
- HttpSession默认使用Cookie存储Session ID,如果用户关掉浏览器接收Cookie的功能,就无法使用Cookie在浏览器存储Session ID。如果在用户禁用Cookie的情况下,仍打算使用HttpSession进行会话管理,那么可以搭配URL重写的,向浏览器响应一段超链接,超链接URL后附加Session ID,当用户单击超链接,将Session ID以GET请求发送给Web应用程序。
- 如果要使用URL重写的方法来发送Session ID,可以使用HttpServletResponse的encodeURL()协助产生所需的URL重写。当容器尝试取得HttpSession实例时,若能从HTTP请求中取得带有Session ID的Cookie,encodeURL()会将传入的URL原封不动地输出。如果容器尝试取得HttpSession实例时,无法是从HTTP请求中取得带有Session ID的Cookie时(通常是浏览器禁用了Cookie),encodingURL()会自动产生带有Session ID的URL重写。
package chapter4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/counter")
public class Counter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
int count = 0;
HttpSession session = req.getSession();
if(session.getAttribute("count") != null)
{
Integer c = (Integer)session.getAttribute("count");
count = c + 1;
}
session.setAttribute("count", count);
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet Count</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet Count "+count+"</h1>");
out.println("<a href='" + resp.encodeURL("counter") + "'>递增</a>");
out.println("</body>");
out.println("</html>");
out.close();
}
}
- 这个程序会有一个超链接,点击超链接会访问同一个URL,在关闭浏览器前,每点击超链接都会使数字递增。如果浏览器没有禁用Cookie,则encodeURL()产生的超链接就是原本的count,如果浏览器禁用了Cookie,则会生成带有Session ID的超链接。
- 如果不使用encodeURL()产生超链接的URL,在浏览器禁用Cookie的情况下,这个程序将会失效。
小心保管Session ID。在HttpSession存活期间,只要有人取得当次的Session ID,在另一浏览器相同的URL附上Session ID,就可以取得同一个HttpSession对象。建议加密敏感信息,并在不使用HttpSession时执行invalidate()明确使之失效。会话阶段重要的操作,最好再做一次身份确认,例如在线转账前再输入一次转账密码。