这篇文章适合刚接触 Web 认证机制的新手,也适合想系统梳理 Session、Cookie、JWT 知识的开发者。我会从三者的定义、存储位置、生命周期出发,结合单体架构、分布式架构的演进,讲清楚它们的区别与适用场景。
一、核心概念速览
1. Cookie
Cookie 是服务器发送给浏览器并保存在客户端的一小段文本数据。
| 属性 | 说明 |
|---|---|
| 存储位置 | 浏览器客户端(SQLite 数据库或文件) |
| 存储内容 | 键值对数据,如 JSESSIONID=ABC123 |
| 大小限制 | 单个 Cookie 约 4KB |
| 生命周期 | 由 Expires 或 Max-Age 控制;未设置则为会话级 Cookie,浏览器关闭即失效 |
| 传输方式 | 通过 HTTP 请求头的 Cookie 字段自动携带;服务器通过 Set-Cookie 响应头设置 |
| 安全特性 | 可设置 HttpOnly(防 XSS 读取)、Secure(仅 HTTPS)、SameSite(防 CSRF) |
Cookie 本身只是"存储介质",不解决认证问题。它最常见的用途是承载 SessionID。
2. Session
Session 是服务器端存储用户会话状态的内存/存储对象。
| 属性 | 说明 |
|---|---|
| 存储位置 | 服务器端(Tomcat 的 ConcurrentHashMap、Redis、数据库等) |
| 存储内容 | 用户身份信息、权限、业务上下文等敏感数据 |
| 大小限制 | 受服务器内存或存储限制 |
| 生命周期 | 由服务器控制(默认 30 分钟超时);可手动销毁 |
| 传输方式 | 客户端只持有 SessionID,实际数据不经过网络传输 |
| 安全特性 | 数据不暴露给客户端,安全性高 |
Session 是"有状态"的认证方案------服务器需要记住每个用户。
3. JWT(JSON Web Token)
JWT 是一种无状态的、自包含的 Token 格式标准。
| 属性 | 说明 |
|---|---|
| 存储位置 | 客户端(localStorage / sessionStorage / Cookie / 内存) |
| 存储内容 | Header(元数据)+ Payload(用户信息)+ Signature(签名) |
| 大小限制 | 比 Cookie 大,通常几 KB |
| 生命周期 | 由 Payload 中的 exp(过期时间)字段控制 |
| 传输方式 | 通常放在 Authorization: Bearer <token> 请求头中 |
| 安全特性 | Payload 仅 Base64Url 编码(非加密),不能存敏感信息;签名防止篡改 |
JWT 是"无状态"的认证方案------服务器不存储会话信息,只负责验证 Token 的签名。
二、三者的本质区别
| 维度 | Cookie | Session | JWT |
|---|---|---|---|
| 存储位置 | 客户端 | 服务端 | 客户端 |
| 是否有状态 | 无状态(存储介质) | 有状态 | 无状态 |
| 数据可见性 | 客户端可见 | 客户端不可见 | 客户端可见(Base64Url 编码) |
| 安全性 | 中(可被篡改/窃取) | 高(数据不暴露) | 中(需签名验证 + HTTPS) |
| 扩展性 | 不涉及 | 分布式需共享存储 | 天然适合分布式 |
| 主动撤销 | 支持(删除/覆盖) | 支持(删除 Session) | 困难(需黑名单机制) |
三、Session + Cookie:单体架构下的典型方案
工作流程
【用户登录】 浏览器 → POST /login ↓ Tomcat 执行 request.getSession() ↓ Tomcat 内部创建 Session
-
生成 SessionID: "ABC123"
-
存入 ConcurrentHashMap: { "ABC123": {userId: 1, role: "ADMIN"} }
-
创建 Cookie: JSESSIONID=ABC123 ↓ 浏览器 ← Set-Cookie: JSESSIONID=ABC123; HttpOnly
【后续请求】 浏览器 → GET /user/profile Cookie: JSESSIONID=ABC123 ↓ Tomcat 解析 Cookie → 查找 sessions.get("ABC123") → 返回用户数据
text
### 架构图
┌─────────────────────────────────────────────────┐
│ 浏览器 │
│ ┌──────────────────────────────────────────┐ │
│ │ Cookie: JSESSIONID=ABC123 │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
│
│ 自动携带 Cookie
▼
┌─────────────────────────────────────────────────┐
│ Tomcat 服务器 │
│ ┌──────────────────────────────────────────┐ │
│ │ ConcurrentHashMap │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ "ABC123" → {userId: 1, role: ADMIN}│ │ │
│ │ └──────────────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
优点
-
实现简单,Tomcat/Undertow 等容器自动管理
-
数据安全,用户无法直接访问 Session 内容
-
支持主动踢人、强制下线等操作
缺点
-
有状态,服务器需要存储每个在线用户
-
分布式环境下 Session 不共享,需额外方案
四、Session + Redis:分布式架构的共享方案
当系统部署在多台服务器时,用户的请求可能被负载均衡到不同机器。Session 默认存储在各自的本地内存,导致用户登录后第二次请求落到另一台服务器时丢失会话。
解决方案:引入 Redis 作为共享 Session 存储
┌─────────────────────────────────────────────────────────┐
│ 浏览器 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Cookie: JSESSIONID=ABC123 │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ 请求可能落到任意一台服务器
▼
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Tomcat-1 │ │ Tomcat-2 │ │ Tomcat-3 │
│ 查 Session │ │ 查 Session │ │ 查 Session │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────────┼────────────────┘
▼
┌───────────────────────┐
│ Redis 集群 │
│ ┌──────────────────┐ │
│ │ "ABC123" → {...} │ │ ← 所有服务器共享
│ └──────────────────┘ │
└───────────────────────┘
Spring Boot 配置示例
java
@Configuration
@EnableRedisHttpSession // 启用 Redis 共享 Session
public class SessionConfig {
// 所有服务器从 Redis 读写 Session,实现共享
}
特点
-
解决了分布式 Session 共享问题
-
但仍然是有状态的------Redis 存储了会话数据
-
引入额外组件(Redis),增加运维复杂度
-
每次请求都要查 Redis,有一定性能开销
五、JWT:彻底走向无状态的分布式方案
Session + Redis 虽然解决了共享问题,但本质还是"有状态"------服务器需要依赖 Redis 存储用户状态。
JWT 则把状态从服务器转移到了客户端。
JWT 的结构
text
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.abc123xyz
↑ ↑ ↑
Header Payload Signature
(元数据) (用户信息) (防篡改签名)
-
Header:声明算法和类型
-
Payload :存储用户信息(如
userId、role、exp过期时间) -
Signature:对前两部分签名,防止篡改
注:三段各自 Base64Url 编码,然后用
.拼接,不是整体编码。
JWT 的工作流程
【用户登录】
浏览器 → POST /login
↓
Spring Boot 验证身份 → 生成 JWT
- Header 转 JSON → Base64Url
- Payload 转 JSON → Base64Url
- 拼接后用密钥签名
↓
浏览器 ← { "token": "eyJhbGciOi...eyJzdWIi...abc123" }
【后续请求】
浏览器 → GET /user/profile
Authorization: Bearer eyJhbGciOi...eyJzdWIi...abc123
↓
任意服务器 → 验证签名 → 解析 Payload → 获取 userId
- 无需查 Redis/数据库
- 无需共享存储
分布式架构下的 JWT 优势
┌─────────────────────────────────────────────────────────┐
│ 浏览器 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 存储 JWT(localStorage / 内存 / Cookie) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ 请求携带 JWT(请求头)
▼
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 服务器-1 │ │ 服务器-2 │ │ 服务器-3 │
│ 验证签名 │ │ 验证签名 │ │ 验证签名 │
│ 解析用户 │ │ 解析用户 │ │ 解析用户 │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└───────────────┴───────────────┘
│
无需共享存储,每台服务器独立工作
为什么 JWT 适合分布式?
| 场景 | Session + Redis | JWT |
|---|---|---|
| 新增服务器 | 需要配置 Redis 连接 | 直接加入集群,无需额外配置 |
| 服务器重启 | Redis 中 Session 仍在 | 无影响(服务器不存储状态) |
| 请求处理 | 每次查 Redis | 只做签名运算(CPU),无 I/O |
| 扩展性 | 受 Redis 连接数限制 | 水平扩展无瓶颈 |
六、最佳实践:双 Token 机制
生产环境中,通常采用 Access Token(短有效期)+ Refresh Token(长有效期) 的组合方案:
text
用户登录
↓
签发 Access Token(15分钟有效期)→ 存在内存中
↓
签发 Refresh Token(7天有效期)→ 存在 HttpOnly Cookie 中
↓
日常请求:携带 Access Token
↓
Access Token 过期 → 用 Refresh Token 换新的 Access Token
| Token 类型 | 存储位置 | 有效期 | 特点 |
|---|---|---|---|
| Access Token | 内存 | 15分钟 | 无状态,短命,降低泄露风险 |
| Refresh Token | HttpOnly Cookie | 7天 | 有状态(可撤销),用于无感刷新 |
这种方案结合了无状态 API 的性能优势和主动撤销的安全管控能力。
七、总结与选型建议
一句话总结
Cookie 是客户端的存储容器,Session 是服务端的有状态会话,JWT 是客户端的无状态凭证。
选型参考
| 应用场景 | 推荐方案 | 理由 |
|---|---|---|
| 单体应用 / 后台管理系统 | Session + Cookie | 实现简单,容器自动管理 |
| 分布式应用(多服务器) | Session + Redis | 共享会话,保留主动控制能力 |
| 微服务 / 前后端分离 | JWT | 无状态,天然适合分布式 |
| 移动端 / 第三方 API | JWT | 跨平台,无 Cookie 依赖 |
| 高安全要求应用 | JWT + Refresh Token | 短生命周期 + 可撤销 |
核心思考:有状态 vs 无状态
text
有状态(Session):服务器说"我记得你"
- 优点:数据安全、可主动撤销
- 缺点:需要存储、分布式需共享
无状态(JWT):服务器说"我不记得你,但你的凭证说明了你是谁"
- 优点:无需存储、天然分布式友好
- 缺点:无法主动撤销、Token 需管理
没有绝对的好坏,只有是否适合当前场景。理解三者的本质,才能在设计系统架构时做出正确的选择。