Cookie 和 Session

Cookie 和 Session

会话技术

  • 客户端浏览器第一次给服务器发送请求,会建立会话,直到某一方断开为止,一次会话中包含多次请求和响应
  • Http 协议是无状态的,也就是多次请求之间无共享数据,但是使用会话技术,可以在一次会话的多次请求间进行数据共享
  • Cookie 是一种客户端会话技术,它将数据保存到客户端,以便于多次请求间数据共享
  • Cookie 的实现基于请求头的 Cookie 和响应头的 Set-Cookie

获取 Cookie 对象,并绑定数据,一个 Cookie 对象可以看做是一个键值对

public Cookie(String name, String value); // Cookie 的构造方法

通过 ServletResponse 对象的 void addCookie(Cookie cookie) 方法发送 Cookie,例如

respons.addCookie(new Cookie("msg","hello cookie"));

通过 ServletRequest 对象的 Cookie[] getCookies() 方法获取 Cookie 数组,例如

Cookie[] cookies = request.getCookies();

实例
在 demo1 中设置 Cookie,在 demo2 中获取 Cookie 并打印

@WebServlet(urlPatterns = {"/cookieDemo1"})
public class CookieDemo1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie c1 = new Cookie("msg1","cookie1");
        Cookie c2 = new Cookie("msg2","cookie2");
        response.addCookie(c1);
        response.addCookie(c2);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

@WebServlet(urlPatterns = {"/cookieDemo2"})
public class CookieDemo2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        for(Cookie c : cookies) {
            System.out.println(c.getName() + " : " + c.getValue());
        }
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

先访问 demo1,再访问 demo2,可以在控制台看到所有的 Cookie 都被成功打印

  • 在默认情况下,当浏览器关闭后,本次会话的 Cookie 数据就会被销毁
  • 可以通过 Cookie 对象的 setMaxAge(int seconds) 方法设置 Cookie 的保存时间
    1. 参数 seconds 设置为正整数,浏览器会将 Cookie 数据写到硬盘的文件中进行持久化存储,并指定该 Cookie 的存活时间,时间到后,Cookie 文件自动失效
    2. 参数 seconds 设置为负整数,默认情况,即关闭浏览器后,Cookie 被销毁
    3. 参数 seconds 设置为 0,告知浏览器删除 Cookie 信息
  • Tomcat8 以后,Cookie 可以存储中文
  • Cookie 不支持存储特殊字符,例如标点和空格,如果把 demo1 中的 Cookie 值改成 "hello cookie1!",则访问该 Servlet 时会直接报 500 错误(实现环境:Tomcat9,Chrome 浏览器)

Cookie特殊字符错误

解决方法是,在存储 Cookie前,先将数据进行 URL 编码,这样就会被还原为原始的字符串

String msg1 = URLEncoder.encode("hello cookie1!","utf-8");
Cookie c1 = new Cookie("msg1",msg1);

此时,该 Cookie 的值是经过 URL 编码后的值,在打印时,需要再次进行 URL 解码

Cookie[] cookies = request.getCookies();
for(Cookie c : cookies) {
    String value = c.getValue();
    if("msg1".equals(c.getName())) {
        value = URLDecoder.decode(c.getValue(),"utf-8");
    }
    System.out.println(c.getName() + " : " + value);
}
  1. 一个 Tomcat 服务器中,部署了多个 Web 项目
    • 默认情况下各个项目之间的 Cookie 不可共享
    • Cookie 对象的 setPath(String path) 方法中的参数 path 默认为当前项目的虚拟目录,如果设置为 /,则可以在多个项目中共享 Cookie
  2. 不同 Tomcat 服务器中,共享 Cookie
    • Cookie 对象的 setDomain(String path) 方法中的参数 path,如果设置为一级域名相同,则不同服务器之间可以共享 Cookie,例如
    Cookie c = new Cookie("msg","hello");
    c.setDomain(".yzt.cool");
    

    则域名 yzt.cool 下的所有二级域名项目如 pan.yzt.coolcfd.yzt.cool 都可以共享该 Cookie,即使部署在不同服务器上

因为 Cookie 是将数据存储在客户端浏览器,且浏览器对单个 Cookie 的大小以及一个域名下的总 Cookie 数量是有限制的,所以 Cookie 一般用于存储少量的、不太敏感、不太重要的数据,例如再不登录的情况下,完成服务器对客户端的身份识别,进行一些不敏感的操作

@WebServlet(urlPatterns = {"/lastVisitTime"})
public class LastVisitTime extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String currentTime = "" + System.currentTimeMillis();
        Cookie[] cookies = request.getCookies();
        response.setContentType("text/html;charset=utf-8");

        if(cookies!=null && cookies.length!=0) {
            boolean flag = false;
            for(Cookie c : cookies) {
                if("LastVisitTime".equals(c.getName())) {
                    long time = Long.parseLong(c.getValue());
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH:mm");
                    String date = sdf.format(new Date(time));
                    response.getWriter().write("<h1>欢迎回来,您上次登录时间为 " + date + "</h1>");
                    c.setValue(currentTime);
                    response.addCookie(c);
                    flag = true;
                    break;
                }
            }
            if(!flag) {
                response.getWriter().write("<h1>您好,这是您第一次登录</h1>");
                Cookie lastVisitTime = new Cookie("LastVisitTime",currentTime);
                lastVisitTime.setMaxAge(3600);
                response.addCookie(lastVisitTime);
            }
        }else {
            response.getWriter().write("<h1>您好,这是您第一次登录</h1>");
            Cookie lastVisitTime = new Cookie("LastVisitTime",currentTime);
            lastVisitTime.setMaxAge(3600);
            response.addCookie(lastVisitTime);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

Session

Session 是一种服务器端会话技术,它可以在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中

HttpSession 对象

获取 HttpSession 对象,从 request 请求 获取

HttpSession session = request.getSession();

使用 HttpSession,HttpSession 是一个域对象,它的对象范围是它可以设置共享数据

Object getAttribute(String name) // 根据键获取共享的对象  
void setAttribute(String name, Object value) // 设置共享数据(键值对)
void removeAttribute(String name)  // 根据键移除共享的对象

Session 的原理

Session 的实现基于 Cookie,当客户端第一次与服务端建立会话,服务端第一次从 request 获取 Session 对象时,服务端会创建一个新的 Session,并为之绑定一个 JSESSIONID(一个不重复的长字符串,假设是 123)作为键,然后在响应消息中创建 set-cookie 头,其值为 JSESSIONID=123,当下一次客户端发送请求时,就会携带该 cookie,服务端获取 Session 对象时,就会根据这个 JSESSIONID 找到之间创建的 Session 对象,以保证一次会话范围内,多次获取的 Session 对象是同一个。

Session原理

Session 的生命周期

  1. 客户端关闭后再次连接服务端,服务端两次获取的 Session 对象在默认情况下不是同一个对象,如果需要相同,则可以手动创建一个 Cookie,该 Cookie 的键设置为 JSESSIONID,同时设置该 Cookie 的最大存活时间,让浏览器持久化保存该 Cookie
HttpSession session = request.getSession();
Cookie cookie = new Cookie("JSESSION",session.getID());
cookie.setMaxAge(60*60);
response.addCookie(cookie);
  1. 客户端不关闭,服务器关闭后再重启,两次获取的 Session 对象不一样,但是为了确保数据不丢失,Tomcat 会自动完成以下工作

    • Session 的钝化:服务器正常关闭之前,将 Session 对象序列化到硬盘持久化保存
    • Session 的活化:服务器重新启动后,会将文件中保存的 Session 读取到内存中。
      这样,虽然服务器重启后的 Session 对象并不是一个,但是可以保证数据不会丢失
  2. Session 对象什么时候会被销毁

    • 服务器关闭
    • Session 对象调用 invalidate() 方法,自我销毁
    • 可以在 Tomcat 的 web.xml 配置文件中修改 Session 的默认失效时间(单位分钟)
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    
    

### Session 的特点

Session 作为一个域对象,可以用于存储一次会话中的多次请求响应的数据,可以存储任意类型任意大小的数据,且存储位置再服务器端,相对于 Cookie 可以存储更多数据而且更加安全。

参考