JavaWeb-Cookie与Session

一、概念

是否还记得我们在HTTP概念中提到:HTTP的一大特点是无状态,这意味着多次HTTP请求之间是无法共享数据的。而在请求之间共享一些数据又是我们期望达到的效果。(例如登录的记住我功能)于是便有了会话跟踪技术,而Cookie与Session就是两种实现方式。

二、Cookie

Cookie:客户端会话跟踪技术,客户端第一次请求服务端时,服务端会生成一个Cookie,并返回给客户端。此Cookie会保存在客户端,并且在以后每次请求中都携带Cookie访问服务端。

(1) 服务端生成Cookie并返回给客户端
package com.byhuang.cookie;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/aServlet")
public class AServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie cookie = new Cookie("name", "value");
        resp.addCookie(cookie);
    }
}
(2)再次请求服务端时,将携带Cookie访问。在服务端接收Cookie:
package com.byhuang.cookie;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/bServlet")
public class BServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            if ("name".equals(cookie.getName())) {
                System.out.println("here:" + cookie.getName() + cookie.getValue());
            }
        }
    }
}
(3) 原理

Cookie的实现是基于HTTP协议的。Web服务器中会在响应头里设置setCookie字段,值为key=value。在上面的例子中,就是setCookie: name=value;当客户端下次请求服务端时,会在请求头中添加Cookie字段,值为所有Cookie的键值对,并用分号分割。如下图所示:

(4) Cookie使用细节

默认情况下,Cookie存储在浏览器的内存中,会随着浏览器的关闭而销毁。但是例如记住我这种功能,不可能要求用户的浏览器保持不关闭。相关API给我们提供了方法:

setMaxAge(int seconds): 参数表示让这个Cookie的存活时间(单位秒),即使关闭浏览器,也会持久化存储在硬盘中,下次打开浏览器Cookie依然是有效的。参数为0表示立即删除这个Cookie,Cookie为负数表示在当前浏览器的内存中,当浏览器关闭时,则Cookie自动销毁,这也是Cookie的默认状态。

三、Session

Session:服务端会话跟踪技术,将数据保存在服务端。Java提供了HttpSession接口,来实现一次会话的多次请求之间的数据共享。一次会话,可以理解为在客户端浏览器不关闭的情况下多次访问服务端。

(1) 服务端生成session
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("/asession")
public class ASession extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        System.out.println(session);
        session.setAttribute("user", "zs");
    }
}
(2)客户端在同一个会话中再次请求服务端时,可以取到同一个session,并取到在上一次请求传递的值。
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("/bsession")
public class BSession extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        System.out.println(session); // 打印的Seesion与第一次请求时打印的相同
        String username = (String) session.getAttribute("user");
        System.out.println(username);  // 取出第一次请求时设置的值,实现了会话间数据共享
    }
}
(3)原理

Session是基于Cookie实现。Web服务器(如Tomcat)会自动把session id以Cookie的形式发送给客户端,客户端在下次请求时,会携带该session id访问。于是在下次获取session时,可以根据这一session id找到在上一次访问的HttpSession对象。而其它客户端的session id与该id不同,于是就实现了在同一会话中,通过session共享数据的目的。

(4) session的使用细节

session的钝化、活化:我们上面提到session是存储在服务端的,也就是session存储在服务器的内存中的。那么在服务器重启后,内存被销毁,session就消失了。但是session有钝化机制,也就是说,当服务器正常关闭时,还存活着的session(在设置时间内没有销毁) 会随着服务器的关闭被以文件(“SESSIONS.ser”)的形式存储在tomcat 的work 目录下,这个过程叫做Session 的钝化。而活化是指重启服务器后,从文件中加载数据到session中。

session的销毁:我们日常生活中也有登出的功能,这里就需要用到session的销毁机制。可以用session.invalidate()方法来实现,读者可自由尝试,笔者在这里不再提供演示。

四、cookie与session的对比
  1. 存储位置:cookie是存储在客户端,session是存储在服务端
  2. 安全性:由于存储位置的区别,因此cookie更不安全
  3. 数据大小:cookie最大是3KB,session无大小限制
  4. 存储时间:cookie可以长期存储,session默认30分钟
  5. 服务器性能:由于session存储在服务端,所以会占用服务器资源

根据以上的对比,我们来看以下几个场景,我们应该使用cookie还是session呢(重要)

(1)早期购物网站的购物车,是在登录前也可以加入商品的。在下次进入网站时,此前加入的商品依然可以显示出来。

分析:这个场景下,肯定是要求客户端被关闭后,再次打开时依然能够取到数据。那这样的话session就不能满足了,因为session id作为cookie存储在客户端,会随着客户端的关闭而销毁(session id通常不会持久化在硬盘中);因此会使用cookie将购物车里的数据持久到本地的硬盘里,下次打开时重新加载进浏览器中。有读者可能会有疑问,session id不是cookie吗,不能把这个cookie的过期时间设置的久一些,持久化在客户端中吗,这样一要知道,session id作为cookie是Web服务器(如Tomcat)替我们完成的,我们不能简便去修改这个动作。当然我们也可以在Web项目中设置全局的session的过期时间,但是这似乎会影响到其它功能,比如说下面第(2)个场景。因此,还是需要使用cookie,而非去修改session的默认超时时间。多说一句,现在购物车的功能大多都修改为存储在数据库中,这也就意味着在登录前取消了加入购物车的这一功能。

(2)登录成功之后的用户数据

分析:由于cookie需要频繁地在客户端与服务端传递,显然是不安全的,而用户信息显然是敏感数据,毫无疑问,这个场景需要使用session。

(3)记住我功能

分析:这个也是需要关闭浏览器再重新打开后依然生效的,因此也需要保存在cookie中。但是这个也需要传递用户数据,因此强行实现这个功能是不安全的,所以目前许多网站都逐渐取消了这人曾经风靡一时的功能。

(4)图片验证码,在注册或登录时,常常会遇到输入完用户名和密码后填写一个图形验证码的场景,比如需要输入图片中的几个数字。

分析:使用图片验证码正是为了有人恶意攻击,比如暴力尝试密码、暴力注册大量用户攻击服务器数据库等。如果使用cookie,相当于把答案传递给了客户端,所以“答案”(数据)只能保存在服务端,使用session。

以上便是对cookie和session的介绍,接下来,笔者将会用本篇的知识来实现一个用户登录、注册、记住我、验证码的小demo,请读者继续关注。