2. 会话管理基础:Session与Cookie原理、无状态Token(JWT)机制、会话生命周期管理
会话管理是SSO与分布式系统的核心基础设施。本章从最基础的Cookie/Session模型出发,逐步过渡到无状态JWT机制,并深入剖析会话生命周期的各个阶段与管控策略。
2.1 Session与Cookie原理
2.1.1 Cookie工作机制
Cookie是服务器存储在客户端(浏览器)的小型文本数据(通常≤4KB),由HTTP响应头Set-Cookie下发,客户端在后续请求中通过Cookie请求头自动携带。
| 属性 | 说明 | 典型值 |
|---|---|---|
| Name=Value | 键值对,存储会话标识 | session_id=abc123 |
| Domain | 指定Cookie可发送的域名范围 | .example.com |
| Path | 限定Cookie生效的URL路径 | / 或 /app |
| Max-Age / Expires | Cookie存活时间(秒/绝对时间) | 3600 或 Session |
| Secure | 仅通过HTTPS传输 | 布尔值 |
| HttpOnly | 禁止JavaScript读取(防XSS) | 布尔值 |
| SameSite | 控制跨站请求携带策略 | Strict / Lax / None |
安全提示:生产环境必须为会话Cookie设置
HttpOnly、Secure、SameSite=Lax 三个属性,防止XSS窃取和CSRF攻击。
2.1.2 Session服务端存储模型
Session是服务端为每个用户维护的临时数据存储区。用户首次访问时,服务端生成唯一Session ID,通过Cookie下发给客户端;后续请求携带该ID,服务端据此查找对应的Session数据。
// 典型Session存储结构(Redis示例)
Key: "session:abc123"
Value: {
"userId": 10086,
"username": "alice",
"role": "admin",
"loginTime": 1710000000,
"expireAt": 1710003600
}
TTL: 3600秒
2.1.3 有状态会话的局限性
- 服务端存储压力:海量用户时Session数据占用大量内存/Redis资源。
- 分布式会话同步:多实例部署需引入集中式Session存储(如Redis)或粘滞会话(Sticky Session),增加架构复杂度。
- 跨域/跨站问题:Cookie受同源策略限制,SSO场景需额外处理跨域Cookie共享。
- CSRF风险:Cookie自动携带特性易被跨站请求伪造利用。
2.2 无状态Token(JWT)机制
2.2.1 JWT结构解析
JSON Web Token(JWT)是一种自包含的令牌格式,由三部分组成,以点号(.)分隔:
// JWT 示例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMDA4NiIsIm5hbWUiOiJhbGljZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxMDAwMDAwMCwiZXhwIjoxNzEwMDAzNjAwfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
| 部分 | 名称 | 内容 | 编码方式 |
|---|---|---|---|
| Header | 头部 | 令牌类型(typ)和签名算法(alg) | Base64URL |
| Payload | 载荷 | 声明(Claims):用户信息、过期时间等 | Base64URL |
| Signature | 签名 | 对前两部分使用密钥进行HMAC或RSA签名 | Base64URL |
安全警告:Payload仅经过Base64编码,并非加密!切勿在JWT中存放密码、信用卡号等敏感信息。必须使用HTTPS传输JWT。
2.2.2 JWT验证流程
- 客户端携带JWT(通常放在
Authorization: Bearer <token>请求头中)。 - 服务端解析Header,获取签名算法(如HS256、RS256)。
- 使用服务端密钥(对称)或公钥(非对称)对Header+Payload重新计算签名。
- 比对计算出的签名与JWT携带的签名是否一致。
- 验证Payload中的
exp(过期时间)、nbf(生效时间)、iss(签发者)等标准声明。 - 验证通过后,直接从Payload中提取用户信息,无需查询数据库或Session存储。
2.2.3 有状态Session vs 无状态JWT对比
| 维度 | Session(有状态) | JWT(无状态) |
|---|---|---|
| 存储位置 | 服务端(内存/Redis/DB) | 客户端(令牌自身) |
| 扩展性 | 需集中式Session存储或粘滞会话 | 天然支持水平扩展,无需共享存储 |
| 跨域支持 | 需配置跨域Cookie或代理 | 通过Authorization头,无跨域限制 |
| 即时吊销 | 直接删除Session数据即可 | 需维护黑名单或缩短过期时间 |
| 性能 | 每次请求需查询存储 | 本地验证,无I/O开销 |
| 令牌大小 | 仅Session ID(小) | 包含完整用户信息(较大) |
2.3 会话生命周期管理
2.3.1 会话阶段划分
| 阶段 | 触发条件 | 关键操作 | 安全关注点 |
|---|---|---|---|
| 创建 | 用户首次访问 / 登录成功 | 生成Session ID / 签发JWT | 使用安全的随机数生成器 |
| 活跃 | 用户持续发送请求 | 验证令牌有效性,刷新过期时间 | 防重放攻击、防会话固定 |
| 过期 | 超过Max-Age / 空闲超时 | 令牌失效,需重新认证 | 及时清理服务端Session数据 |
| 注销 | 用户主动退出 | 删除Session / 加入黑名单 | 确保所有设备/应用同步注销 |
2.3.2 会话超时策略
- 绝对超时(Absolute Timeout):从会话创建开始计算固定时长,到期强制失效。适用于金融、政务等高安全场景。
- 空闲超时(Idle Timeout / Sliding Expiration):每次用户操作后重置计时器,无操作超过阈值则失效。适用于普通Web应用。
- 混合策略:同时设置空闲超时(如30分钟)和绝对超时(如8小时),取先到者。
// JWT 混合超时策略示例
{
"sub": "10086",
"name": "alice",
"iat": 1710000000, // 签发时间
"exp": 1710028800, // 绝对超时:8小时后
"last_active": 1710001800 // 自定义字段:最后活跃时间
}
// 服务端逻辑:每次请求检查 exp 和 last_active + 30分钟
2.3.3 会话吊销机制
无状态JWT的吊销是架构难点,常用方案:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 黑名单 | 将吊销的JWT存入Redis,每次请求检查 | 即时生效 | 增加存储和查询开销,违背无状态初衷 |
| 短有效期 + 刷新令牌 | Access Token有效期短(15分钟),Refresh Token长(7天) | 吊销延迟短,性能好 | 需额外管理Refresh Token |
| 版本号 | 用户表中维护token_version,JWT包含该版本号 | 批量吊销高效 | 每次请求需查DB验证版本号 |
架构建议:在SSO系统中,推荐采用 短有效期Access Token + 长有效期Refresh Token + 黑名单 的组合方案。Access Token用于服务间调用,Refresh Token用于客户端续期,黑名单仅用于紧急吊销场景,降低性能影响。
2.3.4 会话安全最佳实践
- 会话固定防护:用户登录成功后,重新生成Session ID或签发新JWT,禁止复用登录前的令牌。
- 令牌绑定:将JWT与客户端指纹(如User-Agent、IP段、设备ID)绑定,防止令牌被盗用。
- 安全存储:客户端JWT应存储在HttpOnly Cookie或安全存储(如移动端Keychain)中,避免localStorage存储。
- 轮换策略:每次使用Refresh Token时签发新的Refresh Token,旧令牌立即失效。
- 审计日志:记录会话创建、刷新、吊销事件,便于安全审计和异常检测。