Cookie 与 Session

HTTP 是无状态协议——服务器默认不记得"上一次是谁在请求"。但 Web 应用又处处需要"记住用户":登录态、购物车、最近浏览。这一章讲清三套解决方案:Cookie、Session、Token。

1. 问题:无状态协议怎么"记住"用户?

核心思路很简单——给每个用户发一个唯一令牌,让他下次请求带上。服务器据此识别。难点在于:令牌怎么发、怎么存、怎么防伪造。这就是 Cookie + Session / Token 要解决的事。

2. Cookie:浏览器里的钥匙串

Cookie 是浏览器本地存储的一小段数据(每个域名最多约 50 个 Cookie,单条上限约 4KB)。它的工作机制:

# 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=LaxStrict,不要用 None

5. 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 的典型场景:

7. 多服务器共享 Session

单机部署时 Session 放进程内存就行。一旦多台服务器(负载均衡),用户在 A 登录、请求被分到 B 就不认。解决方案:

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 三段:

服务器收到 Token 后用密钥验签,通过即信任 payload 里的用户身份。不用查 DB/Redis

9. Cookie/Session vs JWT:怎么选?

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。两种做法的权衡:

对安全要求高的项目(金融、企业后台)建议 Token 放 HttpOnly Cookie。SPA 公开站可以放 localStorage + CSP。

12. 实战:Node.js Express 实现登录

典型流程:

小结

记住三句话:Cookie 是浏览器存储,Session 是服务器存储,JWT 是无状态 Token。Cookie 一定设 HttpOnly + Secure + SameSite。选型上传统 SSR 用 Session,前后端分离用 JWT,混合方案两全其美。

← 上一篇 请求体与 Content-Type

下一篇 HTTPS 与 SSL/TLS

✈️💬