Session
一、Session简单介绍
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
二、Session和Cookie的主要区别
Cookie是把用户的数据写给用户的浏览器。 Session技术把用户的数据写到用户独占的session中。 Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。
三、session实现原理
3.1、服务器是如何实现一个session为一个用户浏览器服务的?
服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。可以用如下的代码证明:
package com.lovo.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo01 extends HttpServlet {
private static final long serialVersionUID = 1L;
public SessionDemo01() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html;charset=UTF-8");
//使用request对象获取session,如果没有就会直接创建一个
HttpSession session = request.getSession();
//将数据存储到session中
session.setAttribute("data", "朗沃lovo");
//获取sessionID
String sessionID = session.getId();
//判断session是不是新创建的
if(session.isNew()){
response.getWriter().print("你已经新创建了一个session,sessionID是:" + sessionID);
}else{
response.getWriter().print("已经存在session了,sessionID是:" + sessionID);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
点击刷新按钮,再次请求服务器,此时就可以看到浏览器再请求服务器时,会把存储到cookie中的session的Id一起传递到服务器端了,如下图所示:
四、覆盖有效路径,延长session会话时间
默认session在一次浏览器会话时间内,但是,既然session是基于cookie的,cookie是可以长时间的保留的,那么按理来说,session应该也是可以的
可以看到这个服务器默认将JSESSIONID这个cookie的有效路径设置为创建这个Session的web工程根目录。所以我们要覆盖Session中的cookie时也应该设置路径为该web工程根目录。
HttpSession session = request.getSession();
String data = "Session1";
session.setAttribute("data", data);
/*将session的路径,覆盖cookie的路径*/
Cookie cookie = new Cookie("JSESSIONID",session.getId());
cookie.setMaxAge(30*60);
cookie.setPath("/TestDemo");
response.addCookie(cookie);
这样,就算关闭了浏览器,session一样可以保留
五、浏览器禁用Cookie后的session处理
将浏览器设置不能保存cookie,这样,你会发现,每次都将重新生成一个session,session不能保存(当然cookie也不能保存)
4.2、解决方案:URL重写
response.encodeRedirectURL(java.lang.String url) 用于对sendRedirect方法后的url地址进行重写。
response.encodeURL(java.lang.String url)用于对表单action和超链接的url地址进行重写
注意:使用这两个API的前提是必须要先有Session对象
简单来说,你要使用URL重写,就算界面不使用session,那么为了实现URL重写,在代码中也需要加入一句无用的代码 request.getSession()
示例:通过session存取图书购物车
BookData.java
模拟数据字典
package com.lovo.study.urloverwrite;
import java.util.HashMap;
import java.util.Map;
/**
* 模拟数据库数据字典
*/
public class BookData {
private static Map<String, Book> books = new HashMap<String, Book>();
static{
books.put("1", new Book(1, "java教程"));
books.put("2", new Book(2, "php教程"));
books.put("3", new Book(3, "sql教程"));
books.put("4", new Book(4, "c#教程"));
books.put("5", new Book(5, "html教程"));
books.put("6", new Book(6, "js教程"));
}
public static Map<String, Book> getAll(){
return books;
}
}
IndexServlet.java
首页
package com.lovo.study.urloverwrite;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class IndexServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public IndexServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Content-Type", "text/html;charset=UTF-8");
//获取创建session
HttpSession session = request.getSession();
//获取模拟的数据字典
Set<Map.Entry<String, Book>> set = BookData.getAll().entrySet();
//循环Map中所有图书信息
for(Map.Entry<String, Book> e: set ){
Book b = e.getValue();
String url = request.getContextPath() + "/BuyServlet?id=" + b.getId();
//response.encodeURL(java.lang.String url)用于对表单action和超链接的url地址进行重写
url = response.encodeURL(url);
response.getWriter().print(b.getBookName()
+ " <a href='" + url + "'>购买</a><br/>");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
BuyServlet.java
购物中转页面
package com.lovo.study.urloverwrite;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class BuyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public BuyServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取传送过来的图书id
String id = request.getParameter("id");
//根据id 在Map中获取对应书籍
Book book = BookData.getAll().get(id);
HttpSession session = request.getSession();
//获取session中存储的books对象集合
List<Book> books = (List<Book>)session.getAttribute("list");
//如果集合对象为空,创建一个新的集合,放入到session中
if(books == null){
books = new ArrayList<Book>();
session.setAttribute("list", books);
}
//将选中的书籍加入到集合中
books.add(book);
//url重写,防止禁用cookie的情况
String url = response.encodeRedirectURL(request.getContextPath() + "/ListCartServlet");
response.sendRedirect(url);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
ListCartServlet.java
购物车页面
package com.lovo.study.urloverwrite;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class ListCartServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public ListCartServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Content-Type", "text/html;charset=UTF-8");
HttpSession session = request.getSession();
PrintWriter out = response.getWriter();
List<Book> books = (List<Book>)session.getAttribute("list");
if(books == null || books.size() <= 0){
out.print("对不起你还没有添加任何商品");
return;
}
out.print("你购买的商品:<br/>");
for(Book b : books){
out.print(b.getBookName() + "<br/>");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
六、利用Session防止表单重复提交
在服务器端利用session解决表单重复提交的问题
具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。 当前用户的Session中不存在Token(令牌)。 用户提交的表单数据中没有Token(令牌)。
示例:通过session保存令牌实现阻止表单重复提交
FormServle.java
此页面用于生成Token唯一标识,保存在session中,然后跳转到jsp页面
package com.lovo.study.token;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FormServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public FormServlet() {
super();
}
/**
* 注意这种做法需要从servlet跳转到jsp页面
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token = TokenProccessor.getInstance().makeToken();//创建令牌
System.out.println("在FormServlet中生成的token:"+token);
request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
request.getRequestDispatcher("/formToken.jsp").forward(request, response);//跳转到form.jsp页面
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
TokenProccessor.java
生成Token唯一标识的类
package com.lovo.study.token;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import sun.misc.BASE64Encoder;
public class TokenProccessor {
/*
*单例设计模式(保证类的对象在内存中只有一个)
*1、把类的构造函数私有
*2、自己创建一个类的对象
*3、对外提供一个公共的方法,返回类的对象
*/
private TokenProccessor(){}
private static final TokenProccessor instance = new TokenProccessor();
/**
* 返回类的对象
*/
public static 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个字节 md5
try {
MessageDigest md = MessageDigest.getInstance("md5");
byte md5[] = md.digest(token.getBytes());
//base64编码--任意二进制编码明文字符 adfsdfsdfsf
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
PIN.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script>
function changeImage(obj,flag){
obj.src = "${pageContext.request.contextPath }/DrawImageServlet?flag="+flag+"&" + Math.random();
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath }/CheckServlet" method="POST">
数字编码
<input type="text" name="validateCode" />
<img alt="验证码看不清,换一张"
src="${pageContext.request.contextPath }/DrawImageServlet?flag=num"
id="validateCodeImg1" onclick="changeImage(this, 'num')"/>
<br/>
数字字母混合编码
<input type="text" name="validateCode" />
<img alt="验证码看不清,换一张"
src="${pageContext.request.contextPath }/DrawImageServlet?flag=numLetter"
id="validateCodeImg2" onclick="changeImage(this, 'numLetter')"/>
<br/>
汉字编码
<input type="text" name="validateCode" />
<img alt="验证码看不清,换一张"
src="${pageContext.request.contextPath }/DrawImageServlet?flag=chineseChar"
id="validateCodeImg3" onclick="changeImage(this, 'chineseChar')"/>
<br/>
<input type="submit" value="提交" />
</form>
</body>
</html>
CheckTokenServlet.java
验证是否重复提交
package com.lovo.study.token;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CheckTokenServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public CheckTokenServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
if(b){
System.out.println("请不要重复提交");
return;
}
request.getSession().removeAttribute("token");//移除session中的token
System.out.println("处理用户提交请求!!");
}
/**
* 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
* @param request
* @return
* true 用户重复提交了表单
* false 用户没有重复提交表单
*/
private boolean isRepeatSubmit(HttpServletRequest request) {
String client_token = request.getParameter("token");
//1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
if(client_token==null){
return true;
}
//取出存储在Session中的token
String server_token = (String) request.getSession().getAttribute("token");
//2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
if(server_token==null){
return true;
}
//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
if(!client_token.equals(server_token)){
return true;
}
return false;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
Comments