Cookie 与 Session
HTTP 是无状态协议——服务器默认不记得"上一次是谁在请求"。但 Web 应用又处处需要"记住用户":登录态、购物车、最近浏览。这一章讲清三套解决方案:Cookie、Session、Token。
1. 问题:无状态协议怎么"记住"用户?
核心思路很简单——给每个用户发一个唯一令牌,让他下次请求带上。服务器据此识别。难点在于:令牌怎么发、怎么存、怎么防伪造。这就是 Cookie + Session / Token 要解决的事。
2. Cookie:浏览器里的钥匙串
Cookie 是浏览器本地存储的一小段数据(每个域名最多约 50 个 Cookie,单条上限约 4KB)。它的工作机制:
- 服务器通过响应头
Set-Cookie发给浏览器; - 浏览器把它存下来;
- 后续每次请求自动带上(同域名下,无需 JS 介入)。
# 1. 第一次访问 (没登录, 没带 Cookie)
GET / HTTP/1.1
Host: shop.example.com
# 服务器响应: 给你种一个 session Cookie
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
# 2. 后续每次请求,浏览器自动带上 Cookie
GET /cart HTTP/1.1
Host: shop.example.com
Cookie: sessionid=abc123
# 3. 服务器看到 sessionid=abc123, 就知道是 "用户 Alice",
# 把购物车查出来返回3. Set-Cookie 的属性详解
Cookie 不只是 name=value,还有一堆安全属性。生产环境一定要设:
# Set-Cookie 完整属性
Set-Cookie: sessionid=abc123; Domain=.example.com; Path=/;
Expires=Wed, 05 Aug 2026 23:59:59 GMT;
Max-Age=86400;
Secure; HttpOnly; SameSite=Lax
# 各属性含义:
# name=value Cookie 名和值
# Domain=... 生效域名 (默认当前域, .example.com 表示泛子域)
# Path=/ 生效路径 (默认当前路径)
# Expires=... 过期时间 (绝对时间),不设就是 Session Cookie (浏览器关闭就没了)
# Max-Age=... 有效期秒数 (优先级高于 Expires)
# Secure 只通过 HTTPS 传输
# HttpOnly JS 读不到 (防 XSS 偷 Cookie)
# SameSite=Lax 跨站限制 (防 CSRF)4. SameSite:防 CSRF 的关键
# SameSite 三种取值
SameSite=Strict # 完全不带第三方站,最严格,
# 但用户从邮件链接点过来不带 Cookie (用户体验差)
SameSite=Lax # 默认值. GET 导航时带, 其他跨站请求不带 (推荐)
SameSite=None # 任何场景都带 (含 iframe/fetch),
# 必须同时加 Secure, 否则浏览器拒绝现代浏览器(Chrome 80+)默认 Lax,这大幅降低了 CSRF 攻击面。新项目建议显式设 SameSite=Lax 或 Strict,不要用 None。
5. Cookie 的两个类型
- Session Cookie:不设
Expires/Max-Age,浏览器关闭即消失(存在内存里)。 - Persistent Cookie:设了过期时间,存到磁盘,关闭浏览器仍在,到时间才过期。
"记住我"功能通常用一个长期的 Persistent Cookie(如 remember_token),而会话期间的临时身份用 Session Cookie。
6. Session:服务器端的会话表
Cookie 只是载体——它存的是个 sessionid。真正的用户信息存在服务器端(内存、Redis、数据库)。这套方案叫 Session。
# Session (服务器端会话) 工作机制
# 1. 用户登录成功,服务器在内存/Redis 里存:
# sessions["abc123"] = { userId: 42, role: "admin", loginAt: "..." }
# 2. 给客户端发 Cookie: sessionid=abc123
# 3. 客户端下次请求带 Cookie,服务器查 sessions["abc123"] 拿到用户身份
# Session 优点: 服务器随时可以踢人下线 (删 sessions["abc123"] 即可)
# Session 缺点: 多台服务器要共享 session (常用 Redis), 否则用户在 A 登录, B 不认Session 的典型场景:
- 用户登录成功 → 后端生成
sessionid,存到 Redis,发 Cookie。 - 后续请求 → 后端从 Cookie 取
sessionid,查 Redis 拿用户信息。 - 用户登出 → 后端删 Redis 里那条 session。
- 用户改密码 → 后端批量删除该用户所有 session,强制重新登录。
7. 多服务器共享 Session
单机部署时 Session 放进程内存就行。一旦多台服务器(负载均衡),用户在 A 登录、请求被分到 B 就不认。解决方案:
- Session 黏滞(Sticky Session):负载均衡保证同一用户始终路由到同一台。简单但有单点风险。
- 集中存储:所有服务器都连同一个 Redis,Session 放 Redis 里。最常用。
- Cookie 加密存储:把 session 数据加密后塞进 Cookie,服务器无状态。spring-session 默认模式。
8. JWT Token:无状态方案
Session 要服务器存数据,扩展麻烦。JWT(JSON Web Token)换思路:把用户信息直接编码进 Token 发给客户端,服务器不存任何东西。
# JWT (JSON Web Token) — 无状态 Token 方案
# 服务器不存 session, 把用户信息编码进 Token 直接发给客户端
# 客户端把 Token 放在 Authorization 头里发回
# Token 长这样 (header.payload.signature, 三段 Base64URL 用 . 拼起来):
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsImV4cCI6MTcyMjg1NzYwMH0.signature
# 请求时:
GET /api/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
# 服务器验签后,从 payload 里直接拿到 userId=42, 不用查 DB
# 优点: 无状态,易水平扩展,移动端/跨域友好
# 缺点: 没法主动让某个 Token 失效 (除非维护黑名单)JWT 三段:
- Header:算法(如 HS256)+ 类型(JWT)。
- Payload:业务数据(userId、过期时间)。这不是加密,只是 Base64,谁都能解出,所以别放密码。
- Signature:用服务器密钥对前两段签名,防伪造。
服务器收到 Token 后用密钥验签,通过即信任 payload 里的用户身份。不用查 DB/Redis。
9. Cookie/Session vs JWT:怎么选?
- Cookie + Session:传统 Web 应用(SSR、JSP、PHP、Django 模板)。"主动踢人下线"需求强烈。
- JWT:前后端分离、移动端 API、微服务、跨域 SSO。无状态易扩展。
- 混合方案:JWT 存进 HttpOnly Cookie,结合两者优点(前端拿不到 Token 防 XSS,又保留无状态)。
JWT 不是银弹:没法主动失效(要维护黑名单)、payload 膨胀(每次请求都带)、续签麻烦。简单管理系统用 Session 可能更省心。
10. 安全防御要点
# 防御要点
# 1. HttpOnly: 阻止 XSS 读取 Cookie (document.cookie 拿不到)
# 2. Secure: 强制 HTTPS, 防 MITM 抓包
# 3. SameSite=Lax/Strict: 防 CSRF
# 4. 短过期 + Refresh Token: 减小 Token 泄露的影响
# 5. 不要把敏感信息直接放 JWT payload, 它只是 Base64 不是加密
# 6. Cookie 大小有上限 (~4KB), 别塞大数据
# 7. CSRF Token: 服务端发一个一次性 token, 表单提交带上并校验
# 典型 CSRF 攻击场景 (没设 SameSite 时):
# 你在 bank.com 登录后, 又访问 evil.com, evil.com 里有:
# <form action="https://bank.com/transfer" method="POST">
# <input name="to" value="hacker">
# <input name="amount" value="10000">
# </form>
# 浏览器会自动带上 bank.com 的 Cookie, 银行就执行了转账
# 设 SameSite=Lax 之后, 跨站 POST 不带 Cookie, 攻击失败11. localStorage vs Cookie:Token 放哪?
JWT 流行后很多人把 Token 放 localStorage。两种做法的权衡:
- localStorage:JS 能任意读写,灵活但易被 XSS 偷。需要严格的 CSP。
- HttpOnly Cookie:JS 读不到,防 XSS,但要处理 CSRF(用 SameSite)。
对安全要求高的项目(金融、企业后台)建议 Token 放 HttpOnly Cookie。SPA 公开站可以放 localStorage + CSP。
12. 实战:Node.js Express 实现登录
典型流程:
- POST
/login校验账号密码 → 签发 JWT 或创建 Session → 通过Set-Cookie或响应 body 返回。 - 中间件:从 Cookie/Authorization 头取 Token,验证后注入
req.user。 - 受保护路由:检查
req.user,无则 401。 - POST
/logout:清 Cookie + 删 Session。
小结
记住三句话:Cookie 是浏览器存储,Session 是服务器存储,JWT 是无状态 Token。Cookie 一定设 HttpOnly + Secure + SameSite。选型上传统 SSR 用 Session,前后端分离用 JWT,混合方案两全其美。
← 上一篇 请求体与 Content-Type
下一篇 HTTPS 与 SSL/TLS →