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自动携带特性易被跨站请求伪造利用。
Cookie / Session 交互流程 浏览器(客户端) Cookie存储 Web服务器 Session存储 ① 首次请求(无Cookie) ② Set-Cookie: session_id=abc123 ③ 后续请求携带 Cookie: session_id=abc123 查找Session数据 Redis / 内存 ④ 返回用户数据(已登录状态) ⚠ 关键点: • Session ID 通过 Cookie 在客户端与服务端之间传递 • 服务端必须维护 Session 存储(有状态),分布式环境需共享存储

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验证流程

  1. 客户端携带JWT(通常放在Authorization: Bearer <token>请求头中)。
  2. 服务端解析Header,获取签名算法(如HS256、RS256)。
  3. 使用服务端密钥(对称)或公钥(非对称)对Header+Payload重新计算签名。
  4. 比对计算出的签名与JWT携带的签名是否一致。
  5. 验证Payload中的exp(过期时间)、nbf(生效时间)、iss(签发者)等标准声明。
  6. 验证通过后,直接从Payload中提取用户信息,无需查询数据库或Session存储。

2.2.3 有状态Session vs 无状态JWT对比

维度 Session(有状态) JWT(无状态)
存储位置 服务端(内存/Redis/DB) 客户端(令牌自身)
扩展性 需集中式Session存储或粘滞会话 天然支持水平扩展,无需共享存储
跨域支持 需配置跨域Cookie或代理 通过Authorization头,无跨域限制
即时吊销 直接删除Session数据即可 需维护黑名单或缩短过期时间
性能 每次请求需查询存储 本地验证,无I/O开销
令牌大小 仅Session ID(小) 包含完整用户信息(较大)
JWT 无状态验证流程 客户端 存储JWT 服务端 验证签名 + 解析Payload ① 请求携带 Authorization: Bearer <JWT> 验证签名 解析Payload ② 返回业务数据(无需查询Session存储) ✅ 无状态优势: 服务端不保存会话状态,所有用户信息编码在JWT中,天然支持分布式与微服务架构。

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 会话安全最佳实践

  1. 会话固定防护:用户登录成功后,重新生成Session ID或签发新JWT,禁止复用登录前的令牌。
  2. 令牌绑定:将JWT与客户端指纹(如User-Agent、IP段、设备ID)绑定,防止令牌被盗用。
  3. 安全存储:客户端JWT应存储在HttpOnly Cookie或安全存储(如移动端Keychain)中,避免localStorage存储。
  4. 轮换策略:每次使用Refresh Token时签发新的Refresh Token,旧令牌立即失效。
  5. 审计日志:记录会话创建、刷新、吊销事件,便于安全审计和异常检测。