会话管理
Cookie技术
- Cookie技术:会话数据保存在浏览器客户端。
- Cookie核心技术
Cookie类:用于存储会话数据
- 构造Cookie对象
Cookie(java.lang.String name, java.lang.String value)
- 设置cookie
void setPath(java.lang.String uri)
设置cookie的有效访问路径
void setMaxAge(int expiry)
设置cookie的有效时间
void setValue(java.lang.String newValue)
设置cookie的值
- 发送cookie到浏览器端保存
void response.addCookie(Cookie cookie)
发送cookie
- 服务器接收cookie
Cookie[] request.getCookies()
接收cookie
- Cookie的原理
- 服务器创建cookie对象,把会话数据存储到cookie对象中。
new Cookie("name","value");
- 服务器发送cookie信息到浏览器
response.addCookie(cookie);
举例: set-cookie: name=eric (隐藏发送了一个set-cookie名称的响应头)
- 浏览器得到服务器发送的cookie,然后保存在浏览器端。
- 浏览器在下次访问服务器时,会带着cookie信息
举例: cookie: name=eric (隐藏带着一个叫cookie名称的请求头)
- 服务器接收到浏览器带来的cookie信息
request.getCookies();
- Cookie的细节
- void setPath(java.lang.String uri)
设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。
- void setMaxAge(int expiry)
设置cookie的有效时间。
正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。
负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!
零:表示删除同名的cookie数据
- Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
Session技术
- 引入
ookie的局限:
- Cookie只能存字符串类型。不能保存对象
- 只能存非中文。
- 1个Cookie的容量不超过4KB。
如果要保存非字符串,超过4kb内容,只能使用session技术!!!
- Session特点:
会话数据保存在服务器端。(内存中)
- Session核心技术
- 创建或得到session对象
HttpSession getSession()
HttpSession getSession(boolean create)
- 设置session对象
void setMaxInactiveInterval(int interval)
设置session的有效时间
void invalidate()
销毁session对象
java.lang.String getId()
得到session编号
- 保存会话数据到session对象
void setAttribute(java.lang.String name, java.lang.Object value)
保存数据
java.lang.Object getAttribute(java.lang.String name)
获取数据
void removeAttribute(java.lang.String name)
清除数据
- Session原理
问题:服务器能够识别不同的浏览器
前提:在哪个session域对象保存数据,就必须从哪个域对象取出!!!!
浏览器1:(给s1分配一个唯一的标记:s001,把s001发送给浏览器)
创建session对象,保存会话数据
HttpSession session = request.getSession(); -->保存会话数据 s1
浏览器1的新窗口(带着s001的标记到服务器查询,s001->s1,返回s1)
得到session对象的会话数据
HttpSession session = request.getSession(); -->可以取出 s1
新的浏览器1:(没有带s001,不能返回s1)
得到session对象的会话数据
HttpSession session = request.getSession(); --不可以取出 s2
浏览器2:(没有带s001,不能返回s1)
得到session对象的会话数据
HttpSession session = request.getSession(); --不可以取出 s3
代码解读:HttpSession session = request.getSession();
- 第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID
new HttpSession();
- 把JSESSIONID作为Cookie的值发送给浏览器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID); response.addCookie(cookie);
- 第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器
- 服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
if(找到){
return map.get(sessionID);
}
Map<String,HttpSession>]
<"s001", s1>
<"s001,"s2>
- 如果找到对应编号的session对象,直接返回该对象
- 如果找不到对应编号的session对象,创建新的session对象,继续走1的流程
结论:通过JSESSION的cookie值在服务器找session对象!!!!!
- Sesson细节
- java.lang.String getId()
得到session编号
- 两个getSession方法:
getSession(true) / getSession() :
创建或得到session对象。没有匹配的session编号,自动创建新的session对象。
getSession(false)
得到session对象。没有匹配的session编号,返回null
- void setMaxInactiveInterval(int interval)
设置session的有效时间
session对象销毁时间:
默认情况30分服务器自动回收
修改session回收时间
全局修改session有效时间
<!-- 修改session全局有效时间:分钟 --><session-config><session-timeout>1</session-timeout></session-config>
手动销毁session对象
void invalidate()
销毁session对象
- 如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题
/*** 手动发送一个硬盘保存的cookie给浏览器
*/
Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
经常遇到的问题解决方法
- IE禁用Cookie之后的Session处理
解决方法:URL重写
response.encodeRedirectURL(java.lang.String url)
用于对sendRedirect方法的URL重写
response.encodeURL(java.lang,String url)
用于对表单action或超链接的url地址重写
- 利用session防止表单重复提交
具体的做法:
在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)。
实例
创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面
protectedvoid doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token = TokenProccessor.getInstance().makeToken();//创建令牌16
System.out.println("在FormServlet中生成的token:"+token);
request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)18
request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面19
}
在form.jsp中使用隐藏域来存储Token(令牌)
<%@ pagelanguage="java"import="java.util.*"pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>form表单</title></head><body><form action="${pageContext.request.contextPath}/servlet/DoFormServlet"method="post"><%--使用隐藏域存储生成的token--%><%--
<inputtype="hidden"name="token"value="<%=session.getAttribute("token") %>">
--%>
<%--使用EL表达式取出存储在session中的token--%><input type="hidden"name="token"value="${token}"/>
用户名:<input type="text"name="username"><input type="submit"value="提交"></form></body></html>
DoFormServlet处理表单提交
publicvoid doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean b = isRepeatSubmit(request);//判断用户是否是重复提交if(b==true){
System.out.println("请不要重复提交");
return;
}
request.getSession().removeAttribute("token");//移除session中的token
System.out.println("处理用户提交请求!!");
}
/**
* 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
* @param request
* @return
* true 用户重复提交了表单
* false 用户没有重复提交表单
*/privateboolean isRepeatSubmit(HttpServletRequest request) {
String client_token = request.getParameter("token");
//1、如果用户提交的表单数据中没有token,则用户是重复提交了表单if(client_token==null){
returntrue;
}
//取出存储在Session中的tokenString server_token = (String) request.getSession().getAttribute("token");
//2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单if(server_token==null){
returntrue;
}
//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单if(!client_token.equals(server_token)){
returntrue;
}
returnfalse;
}
生成Token的工具类TokenProccessor
publicclassTokenProccessor {
/*
*单例设计模式(保证类的对象在内存中只有一个)
*1、把类的构造函数私有
*2、自己创建一个类的对象
*3、对外提供一个公共的方法,返回类的对象
*/privateTokenProccessor(){}
privatestaticfinal TokenProccessor instance = new TokenProccessor();
/**
* 返回类的对象
* @return
*/publicstatic TokenProccessor getInstance(){
return instance;
}
/**
* 生成Token
* Token:Nv6RRuGEVvmGjB+jimI/gw==
* @return
*/public String makeToken(){ //checkException// 7346734837483 834u938493493849384 43434384
String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
//数据指纹 128位长 16个字节 md5try {
MessageDigest md = MessageDigest.getInstance("md5");
byte md5[] = md.digest(token.getBytes());
//base64编码--任意二进制编码明文字符 adfsdfsdfsf
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
thrownew RuntimeException(e);
}
}
}