在人机交互过程中,会话管理是指保持用户的整个会话活动的交互与计算机系统跟踪的过程。会话管理分为桌面会话管理、浏览器会话管理、Web会话管理。本书讨论的是Web会话管理(通常指的是session以及Cookie) , 也称为会话跟踪。
会话管理基本原理
使用隐藏域
隐藏域是指将表单中的内容在显示页面时隐藏, 即不显示数据。在JSP中将input标签的type 属性值设定为hidden, 即生成一个隐藏表单域。再将会话的唯一标识记录到隐藏域中的value属性中, 并设定name属性值。当表单提交时, 会话标识也被提交到服务端, 服务端根据它找到对应的会话对象。
使用隐藏域时,需要在每个页面中都包含会话标识表单,这样才能在许多页面跳转之间保存会话,并对它进行管理。此方法实现起来比较烦琐,安全性较差,不适合隐秘性的数据,在目前的开发中较少使用。
使用Cookie
利用Cookie实现会话管理是最容易的实现方式, 也是使用较多的方法。其原理是在服务端保存的会话对象中设定会话的唯一标识, 客户端将会话标识保存在Cookie中, 当浏览器发起请求时,从Cookie中取得会话标识并发送给服务端, 服务端在收到请求后, 根据发送过来的会话标识查找到对应的会话对象,这样服务端就清楚当前是哪个客户端在连接,并且可以从会话中获得信息。
利用Cookie实现会话管理是目前开发中采用的主流方法, 大多数的动态页面开发技术都实现了这一功能,并且其管理流程是自动完成的,在实现上并不需要多大的开发工作量。其实现会话管理的过程如图所示。
使用URL重写
顾名思义, URL重写是指在URL地址的末尾添加会话标识, 改写了原先的URL地址。其本质是用于唯一标识会话的信息, 以参数的形式添加到URL中, 服务端接收到请求时, 解析出会话标识, 然后利用会话标识查找出与当前请求对应的会话对象。该方法是用在浏览器中Cookie被禁用的情况下,利用该方法可以很好地实现会话跟踪而不会受到浏览器参数设定的影响。
利用URL重写的方法, 其特点是在URL后面添加会话标识, 因此整个Web应用中的超链接或者脚本中用到的URL都需要添加会话标识, Web应用中的每一个页面都需要动态生成, 页面中的每一个链接或者由客户端生成的跳转指令都必须加上会话标识,这样才能确保是当前会话。当客户端访问静态页面时,会话标识将会丢失,当重回动态页面时将不能继续此前的会话,以上是它的特点,也是它的缺陷。
利用该方法还有一个问题, 即当用户在浏览网页时, 可能会将URL复制下来分享给朋友, 因为URL中会包含会话标识, 那么其他人可能与当前浏览者使用同一会话对象, 这样当前浏览者的个人隐私就暴露了,信息不再安全。
无论采用哪种方式实现会话管理,在服务端都需要完成相关代码才能完整地实现会话管理的任务。这些任务主要是生成唯一的会话标识、存储会话对象、从Web容器中取得当前请求的会话、回收空闲会话。
HTTP协议是无意识、单向协议。服务端不能主动连接客户端,只能等待并答复客户端请求。客户端连接服务端, 发出一个HTTP请求, 服务端处理请求, 并返回一个HTTP响应给客户端, 至此, 本次会话结束, 从这一过程可以看出, HTTP协议本身并不支持服务端保存客户端的状态等信息。于是, Web服务器中引入了session的概念, 用来保存客户端的信息。
Session会话管理
使用HttpSession管理会话
在Java中, 使用javax.servlet.http.HttpSession类来实现session会话。每个请求者对应一个session 对象, 客户的所有状态信息都保存在该对象里。当用户第一次请求服务器时, 就创建了session对象。它以key-value 的形式进行保存,通过相对应的读写方法来保存客户的状态信息,例如getAttribute(String key) 用于获得参数、setAttribute(String key, Object value) 用于设定参数和参数值。在Servlet中可以通过request.get Session() 方法获取客户的session对象, 例如:
HttpSession session = request.getSession();
session.setAttribute("username","john");
在JSP中内置了Session对象, 可以直接使用, Servlet使用上述方法获取session对象。若JSP 页面中设定了<%@page session=“false”%>, 则该JSP页面的session对象是不可用的。下面通过例子说明如何利用session来保存登录信息。
login.jsp
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" %>
<%
String userName = (String) session.getAttribute("username");
String basePath = request.getContextPath();
if (userName != null && userName != "") {
response.sendRedirect(basePath + "/index.jsp");
return;
}
%>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>用户登录</h1>
<form action="<%=basePath%>/login" method="post">
<p>
<label>输入用户名:</label>
<input type="text" name="name">
</p>
<p>
<label>输入密码:</label>
<input type="password" name="pwd">
</p>
<p>
<input type="submit" value="提交">
</p>
</form>
</body>
</html>
loginServlet.java
package com.wujialiang;
import javax.servlet.RequestDispatcher;
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;
@WebServlet(name = "login",urlPatterns = {
"/login"
})
public class loginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("name");
String pwd = req.getParameter("pwd");
String basePath = getServletContext().getContextPath();
if(userName.equals("admin")&&pwd.equals("123456")){
HttpSession session = req.getSession();
session.setAttribute("username",userName);
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/index.jsp");
requestDispatcher.forward(req,resp);
}else{
//错误跳转登录页面
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/login.jsp");
requestDispatcher.forward(req,resp);
}
}
}
index.jsp
<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" %>
<%
String userName = (String) session.getAttribute("username");
String basePath = request.getContextPath();
if (userName == null || userName == "") {
response.sendRedirect(basePath + "/login.jsp");
return;
}
%>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>欢迎你:<%=userName%>
</h1>
</body>
</html>
HttpSession管理会话的原理
HttpSession 会话管理是利用服务器来管理会话的机制,当程序为某个客户端的请求创建了一个session的时候, 服务器会检查客户端的请求是否已经包含了一个session标识, 如果已经有了session标识, 服务器就把该session检索出来使用; 如果请求不包含session标识, 就为客户端创建一个该请求的唯一session标识。其会话管理流程如图所示。
HttpSession与URL重写
提到URL重写是用在客户端不支持Cookie的情况下。它的实现方式是将Session的标识id添加到URL中。服务器通过解析重写后的URL获取Session的标识id。这样即使客户端不支持Cookie, 也可以用Session来记录状态信息。重写的方法如下:
response.encodeURL("login.jsp");
//或者
response.sendRedirect(response.encodeURL("login.jsp"));
这两种方法的效果是一样的, 如果客户端支持Cookie, 那么生成的URL不变; 如果不支持,生成的URL中就会带有jsessionid字符串的地址, 超链接如图所示。
从图中可以看出, 在URL的文件名后面, 参数的前面添加了字符串“; jsessionid=标识id”o 用户单击这个链接时, 客户端会把session的id值通过URL提交到服务器上, 服务器通过解析URL 地址获得session的id值。
HttpSession中禁用Cookie
可以通过配置的方式在Web项目中禁用Cookie, 禁用方法如下:
在webapp目录下新建META-INF文件夹,打开或者新建context.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Context cookies="false">
</Context>
如果想全局配置需要修改tomcat下的context.xml
上述两种方法都是在Context元素中添加属性cookies=“false”, 二者的区别在于前者是对单个项目的Cookie禁止, 后者是禁止部署在Tomcat服务器里的Web项目使用Cookie。
HttpSession的生命周期
Servlet有它的生命周期, 同样, HttpSession也有其生命周期:创建→使用→消亡。
HttpSession对象的创建
当客户端第一次访问服务器时, 服务器为每个浏览器创建不同Session的ID值。在服务器端使用request.getSession() 或者request.get Session(true) 方法来获得HttpSession对象。
HttpSession对象的使用
创建HttpSession对象后, 使用Session对象进行数据的存取和传输。具体的过程如下:
- 将产生的sessionID存入Cookie中。
- 当客户端再次发送请求时, 会将sessionID与request一起传送给服务器端。
- 服务器根据请求过来的sessionID与保存在服务器端的session对应起来,判断是否为同session。
HttpSession对象的消亡
有如下3种方式可以结束session对象:
- 将浏览器关闭。
- 调用HttpSession的invalidate() 方法。
- session超时。
关闭浏览器, 这样会使浏览器端的session失效, 服务器端session并不会失效。如果服务器进程终止了, 那么session会被结束。
在session结束时, 服务器会清空当前浏览器的相关数据信息。
以上就是HttpSession的生命周期过程, 在请求处理时不断循环第2步, 直到session对象消亡。session是保存在服务器内存中的, 每个用户都有一个独立的session。session中的内容应该尽量少,这样当有大量客户访问服务器时才不会导致内存溢出。
只有访问JSP、Servlet时才会创建session, 访问静态页面时是不会创建session对象的。
HttpSession的有效期
通常情况下, 都给session设定一个有效期, 当某用户访问的session超过这个有效期时, 即不是活跃的session, 那么session就失效了, 服务器会将它从内存中清除。设定session的有效期有以下三种方法:
- 在对应的Web服务器配置中设置所有session的有效期。
- 调用session的setMaxInactiveInterval(long interval) 进行设定。
- 在web.xml中修改, 例如:
<session-config>
<!--会话超时时长30分钟-->
<session-timeout>30</session-timeout>
</session-config>
调用session的invalidate() 可以销毁session.