06月07, 2019

Java Web--Servlet--Session

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应该也是可以的 -w575

可以看到这个服务器默认将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);

-w797

这样,就算关闭了浏览器,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);
    }

}

本文链接:http://www.yanhongzhi.com/post/session.html

-- EOF --

Comments