Cookie、Session、JWT:你该用哪种?
一句话结论(30s)
Cookie 是「传输载体」、Session 是「服务端存状态」、JWT 是「自包含无状态令牌」,三者不是替代而是互补——因为 Cookie 只负责把凭证带在请求里,Session 和 JWT 才是状态管理策略,现代架构常用「JWT + HttpOnly Cookie + 短有效期 + Refresh Token」兼顾安全与分布式。
核心原理(2min)
- Cookie:服务器通过
Set-Cookie下发、浏览器自动携带,是「载体」;安全靠HttpOnly(防 XSS 偷 Cookie)、Secure(仅 HTTPS)、SameSite(防 CSRF)。 - Session:客户端只存 SessionID,真实数据(用户、权限)在服务端(Redis/DB),可主动销毁,但集群需共享存储。
- JWT:
Header.Payload.Signature,服务端只验签不查库、不存状态,天然适合分布式;但无法主动失效、Payload 仅 Base64 明文、体积较大。 - 选型:单体用 Session+Redis;微服务/移动端用 JWT;高安全场景用 HttpOnly Cookie + Session。
底层深入(5-10min)
Cookie:存在客户端的”通行证”
Cookie 是服务器通过 Set-Cookie 响应头命令浏览器存储的一小段数据(最多 4KB),浏览器在后续请求中自动通过 Cookie 头带回。
浏览器 服务器
| |
|←── Set-Cookie: sessionId=abc ──| 服务器: 记住这个凭证
| |
|── Cookie: sessionId=abc ──────→| 浏览器: 自动带上
自动携带是 Cookie 最方便也最危险的特点——CSRF 攻击正是利用了这一特性。
思考:为什么「自动携带」既是优点又是危险?因为浏览器在每次请求时无条件带上 Cookie,省去了手动传凭证的麻烦,但也意味着攻击者只要诱导用户在已登录状态下点一个链接,浏览器就会把 Cookie 连同请求发往攻击者指定的站点——CSRF 正是钻了「浏览器替你代劳」的空子,所以才有
SameSite来限制跨站携带。
安全属性:
HttpOnly:禁止 JavaScript 访问(防 XSS 偷 Cookie)Secure:仅 HTTPS 传输SameSite=Strict/Lax/None:控制跨站请求是否携带 Cookie(防 CSRF)
Session:存在服务端的”状态”
Session 是一种服务端状态存储,客户端的 Cookie 中只存一个 Session ID(随机字符串)。真正的用户数据(登录用户、权限等)存在服务端(内存/Redis/数据库)。
客户端 Cookie: sessionId = "abc123"
服务端 Redis: "session:abc123" = {userId: 1001, name: "张三", roles: ["admin"]}
优点:敏感数据不暴露在客户端;可主动销毁(删除 Redis 中的 key) 缺点:集群环境下需共享 Session(Redis 集中存储),增加架构复杂度
思考:为什么集群环境下 Session 就成了负担?因为 Session 状态存在服务端,而负载均衡会把请求打到任意一台机器;如果每台机器各存各的 Session,用户下一次请求落到别的机器上就「失忆」了。所以必须把 Session 抽到共享存储(Redis),这等于给无状态的服务集群强行加了一层有状态依赖,架构复杂度随之上升。
JWT:自包含的无状态令牌
JWT(JSON Web Token)是一个自包含的身份令牌,由三部分组成:
Header.Payload.Signature
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "1001", "name": "张三", "exp": 1234567890, "iat": 1234560000}
Signature: HMAC-SHA256(base64(Header) + "." + base64(Payload), secret)
服务端只验签,不查库,不存状态。任意节点都可以独立验证。JWT 天然支持分布式——每个微服务用同一密钥验签即可,不需要访问共享存储。
优点
- 无状态:服务端零存储,扩展性好
- 跨语言:JSON 格式,任何语言可解析
- 跨域友好:不依赖 Cookie
缺点
- 无法主动失效:签发后有效期内始终有效(除非维护黑名单),用户修改密码后旧 Token 仍然可用
- Payload 明文:仅 Base64 编码,不加密,敏感信息不应存入
- 体积大:比 Session ID 大得多(通常 ~1KB vs ~32B),每次请求都要传输
思考:JWT 的「无状态」怎么反而变成了缺点?因为无状态意味着服务端不记录「哪些 Token 已经作废」,签出去的 Token 在有效期内就是「既成事实」。用户改了密码、账号被盗,服务端想立刻踢下线却无从下手,只能靠缩短有效期或维护黑名单来补救——这就是用「零存储」换「灵活性」的代价。
选型指南
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单体应用 | Session + Redis | 可主动销毁,管理方便 |
| 微服务架构 | JWT + 短有效期 | 无状态,各服务独立验证 |
| 移动端 App | JWT (localStorage) | 无 Cookie 环境 |
| 高安全场景 | HttpOnly Cookie + Session | 双重防护 XSS + CSRF |
| 需要立即失效 | Session | 服务端删除即可 |
JWT 泄露怎么办?
JWT 一旦泄露(如 XSS 攻击偷走 localStorage 中的 Token),攻击者可以在有效期内伪装用户。唯一的防线是让 Token 尽快过期。
标准做法:
- Access Token 短有效期(15 分钟)
- Refresh Token 长有效期(7 天),且存在 HttpOnly Cookie 中防 XSS
- Token 泄露后,将 Refresh Token 加入黑名单强制失效
总结
三者不是替代关系,而是互补关系。Cookie 是传输载体,Session 和 JWT 是状态管理策略。现代架构中常用的模式是:JWT + HttpOnly Cookie + 短有效期 + Refresh Token——兼顾安全性和分布式友好性。
章末提问
Q1:Cookie 和 Session 是什么关系?JWT 又是什么?
结论先行:Cookie 是传输载体,Session 和 JWT 是两种不同的状态管理策略。因为 Cookie 只是浏览器自动携带的一小段数据,本身不承载业务状态;Session 把真实数据放服务端、客户端只留一个 SessionID,JWT 则把用户信息自包含地签进令牌、服务端只验签不存储,所以三者不是替代而是互补,现代架构常组合使用。
Q2:JWT 为什么无法主动失效?有什么补救办法?
结论先行:因为 JWT 无状态,服务端不记录已签发的令牌,无法「撤销」一个还没过期的 Token。因为验证只靠签名,签出的 Token 在有效期内对任何持有者都有效;补救只能绕道——把有效期压到 15 分钟级别、用 Refresh Token 换新、把泄露的 Token/Refresh Token 加入黑名单强制失效。
Q3:分布式架构里为什么更倾向 JWT 而不是 Session?
结论先行:因为 JWT 自包含、无状态,各节点用同一密钥即可独立验签,无需访问共享存储。因为 Session 要求集群共享一份状态(Redis),引入网络依赖和单点;而 JWT 把身份信息放在令牌里,服务扩容、跨服务调用都不需要额外协调,天然贴合微服务的无状态假设。