楼层: 首页/ 软件技术/ JavaEE / Jakarta EE/ Cookie 与 Session:服务器怎么记住你是谁
07

Cookie 与 Session:服务器怎么记住你是谁

Session Tracking · Cookie Attributes · Distributed Session

HTTP 是"无状态"的——服务器处理完一个请求,就不记得你是谁了。你登录了一下,刷新又让你登录?用户会骂娘。Cookie 和 Session 就是解决"跨请求记住用户"的两件套。

论一图理解 Cookie + Session 配合

第 1 次访问:服务器在内存里给你建一个 Session(存你的登录信息),并给你发一张写着 SessionId 的小纸条(Cookie),让浏览器存着。

第 2 次起:浏览器每次请求都自动把小纸条带上,服务器一看 SessionId:"哦,是你,登录信息还在。"——你就不用重新登录了。

区别记法:Cookie 存在用户浏览器(小,不安全),Session 存在服务器(大,占内存)。Cookie 是"门票",Session 是"后台档案"。

登录成功写 Session,过滤器里校验

// 登录 Servlet 里,账号密码对了之后:
HttpSession session = req.getSession();   // 没有就新建一个
session.setAttribute("loginUser", user);   // 把用户对象塞进 Session
session.setMaxInactiveInterval(30 * 60); // 30 分钟不操作自动失效

// Cookie 手动写一个(比如"记住我 7 天"):
Cookie c = new Cookie("token", tokenValue);
c.setMaxAge(7 * 24 * 60 * 60);   // 7 天
c.setHttpOnly(true);             // JS 读不到,防 XSS 偷 Cookie
c.setSecure(true);               // 只在 HTTPS 下传输
c.setAttribute("SameSite", "Strict"); // 跨站请求不带 Cookie,防 CSRF
resp.addCookie(c);

Cookie 常用属性:安全三件套一个都不能少

属性作用 & 为什么
MaxAge活多久(秒)。负数=会话级(关浏览器就没),0=立刻删除。
HttpOnlyJavaScript 读不到这个 Cookie。没有它,XSS 一打,你的登录 Cookie 就被偷了。
Secure只在 HTTPS 传输。HTTP 下发它,等于半路裸奔。
SameSiteStrict/Lax/None。控制跨站请求带不带 Cookie,主要防 CSRF。
Path / Domain限定 Cookie 在哪些路径 / 域名下生效,别到处乱发。

Session 什么时候没:三种死法

情况说明
超时自动失效超过 setMaxInactiveInterval 没活动,容器自动回收。
主动登出代码里 session.invalidate(),立刻清空。
服务器重启默认存在内存里,重启就没——这也是要分布式 Session 的原因之一。

用户禁用 Cookie 怎么办:URL 重写(了解即可)

// 万一用户把浏览器 Cookie 关了,SessionId 传不过去。
// 老办法:把 SessionId 拼在 URL 后面,容器自动处理:
String link = resp.encodeURL("/board");
// 输出可能变成 /board;jsessionid=ABC123(现在基本没人用,了解即可)
分布式 Session 问题

Session 存在哪台 Tomcat 的内存里,就只有那台认识你。一旦部署成"多台 Tomcat 集群",用户请求被负载均衡分到另一台,那边没他的 Session——又要登录了。解决方案:① Session 粘滞(负载均衡器固定把同一个用户打到同一台,治标不治本);② Session 集中存储(把 Session 放 Redis,所有机器共享,生产主流);③ 干脆不用 Session,用 JWT Token(无状态)。这也是现代前后端分离架构抛弃 Session 的原因。

完整案例:登录写 Cookie + Session + 分布式 Session 思路

// 登录成功后:写 Session + 写"记住我"Cookie
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException, ServletException {
        String user = req.getParameter("username");
        String pwd  = req.getParameter("password");

        if ("admin".equals(user) && "123456".equals(pwd)) {
            // ① 写 Session:服务器内存存登录态
            HttpSession session = req.getSession();
            session.setAttribute("loginUser", user);
            session.setMaxInactiveInterval(30 * 60);  // 30 分钟过期

            // ② 防 Session 固定攻击:登录后换新 SessionId
            req.changeSessionId();

            // ③ "记住我 7 天":写一个安全的 Cookie
            Cookie remember = new Cookie("remember", java.util.UUID.randomUUID().toString());
            remember.setMaxAge(7 * 24 * 60 * 60);
            remember.setHttpOnly(true);   // JS 读不到,防 XSS
            remember.setSecure(true);     // 只 HTTPS 传
            remember.setAttribute("SameSite", "Strict");
            resp.addCookie(remember);

            resp.sendRedirect("/home");
        } else {
            req.setAttribute("error", "用户名或密码错");
            req.getRequestDispatcher("/login.jsp").forward(req, resp);
        }
    }
}

// 登出:销毁 Session
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        HttpSession session = req.getSession(false);  // 没有就不新建
        if (session != null) session.invalidate();  // 立刻清空
        resp.sendRedirect("/login.jsp");
    }
}

// 分布式 Session 思路(多台 Tomcat 集群):
// 方案 A:Session 粘滞(Nginx 把同一用户固定打到同一台 Tomcat)
// 方案 B:Session 集中存 Redis(Spring Session 实现,所有机器共享)
// 方案 C:不用 Session,用 JWT Token(无状态,服务器不存)

浏览器禁用 Cookie 怎么办:URL 重写兜底

Session 靠 Cookie 存 JSESSIONID,但万一用户浏览器把 Cookie 禁了,Session 就废了。Servlet 提供URL 重写兜底:把 SessionId 直接拼在 URL 尾巴上,比如 /list;jsessionid=ABC123。

// 响应里:用 encodeURL 自动给 URL 拼上 SessionId(Cookie 没禁时它原样返回)
// 注意是 ;jsessionid= 不是 ? ,这是路径参数不是查询串
String link = resp.encodeURL("/list");
// 浏览器支持 Cookie 时返回 /list;禁用时返回 /list;jsessionid=xxx
resp.getWriter().println("<a href=\\" + link + "\\">留言列表</a>");

// 重定向地址也要 encodeRedirectURL
resp.sendRedirect(resp.encodeRedirectURL("/home"));

论实际还要不要学

URL 重写有安全隐患(SessionId 出现在 URL 里,容易被浏览器历史、Referer 泄漏),现代 Web 基本都用 JWT / Token 放 Authorization 头,已经不靠这套了。知道"Session 还有这种兜底机制、SessionId 别放 URL"这两点就够,面试能答上即可。

章末面试 · Cookie 与 Session(4 题)

1.(概念题)Cookie 和 Session 区别?谁存在哪?

查看答案

答案:Cookie 存在浏览器(小、不安全),Session 存在服务器内存(大、占内存)。Cookie 是门票(SessionId),Session 是后台档案。

2.(概念题)HttpOnly Cookie 防什么?

查看答案

答案:防 XSS 攻击偷 Cookie。没有 HttpOnly,JS 能读 document.cookie,脚本一注入就把登录态偷走。

3.(概念题)分布式部署 Session 为什么是问题?怎么解决?

查看答案

答案:Session 存在单机内存,负载均衡分到另一台就不认你。解法:① Session 粘滞;② Session 集中存 Redis;③ 不用 Session 用 JWT。

4.(安全题)为什么登录成功后要 changeSessionId()?

查看答案

答案:防 Session 固定攻击。攻击者事先给你种一个 SessionId,你登录后这个 SessionId 就有了你的登录态,攻击者拿它就能冒充。登录后换新 SessionId,旧的就废了。