2、令牌生命周期管理:Access Token与Refresh Token的生成、过期策略、刷新机制及安全存储要求
令牌生命周期管理是OAuth 2.0授权体系的核心安全防线。Access Token与Refresh Token承担不同职责:前者用于快速验证资源访问权限,后者用于无感续期。本章从生成、过期、刷新到存储,逐层拆解令牌全生命周期的设计要点与工程落地规范。
2.1 令牌类型与职责划分
OAuth 2.0定义两种核心令牌,其生命周期策略必须差异化设计:
| 维度 | Access Token | Refresh Token |
|---|---|---|
| 用途 | 携带至资源服务器,证明客户端有权访问受保护资源 | 在Access Token过期后,向授权服务器换取新的Access Token |
| 有效期 | 短(通常15分钟~1小时) | 长(通常7天~30天,甚至更长) |
| 传输频率 | 每次API请求均携带 | 仅在令牌刷新时传输 |
| 泄露影响 | 攻击者可在有效期内冒充用户访问资源 | 攻击者可长期续期,持续窃取访问权限 |
| 存储要求 | 内存/短期缓存,避免持久化 | 安全存储(如HttpOnly Cookie、Keychain) |
设计原则:Access Token应“短命且无状态”,Refresh Token应“长命但有状态”。授权服务器必须维护Refresh Token的状态(如是否已撤销、是否单次使用),而Access Token可完全依赖签名验证,无需服务端存储。
2.2 令牌生成规范
令牌的生成必须满足不可预测性、防篡改、可验证三个安全属性。以下是两种主流生成方式:
2.2.1 自包含令牌(JWT)
JWT(JSON Web Token)将用户身份、权限范围、过期时间编码在令牌本身,资源服务器无需回查授权服务器即可完成验证。典型Payload结构:
{
"iss": "https://auth.example.com",
"sub": "user_12345",
"aud": "https://api.example.com",
"exp": 1718000000,
"iat": 1717996400,
"scope": "read write",
"client_id": "web_app_01"
}
- 签名算法:推荐使用RS256(RSA-SHA256)或ES256(ECDSA-SHA256),避免使用HS256(对称密钥)在分布式环境下泄露密钥。
- JTI(JWT ID):为每个Access Token生成唯一标识,用于黑名单或日志追踪。
- 避免敏感数据:JWT Payload仅用Base64编码,非加密,严禁放入密码、手机号等PII。
2.2.2 不透明令牌(Opaque Token)
不透明令牌是一串随机字符串(如UUIDv4或CSPRNG生成的32字节序列),授权服务器需维护令牌到用户信息的映射表。适用于需要服务端即时撤销的场景。
// 生成示例(Node.js crypto)
const crypto = require('crypto');
const opaqueToken = crypto.randomBytes(32).toString('hex');
// 输出: "a1b2c3d4e5f6...(64位十六进制字符串)"
选型建议:高并发、低延迟场景优先选择JWT(无状态验证);需要即时撤销、令牌轮换、审计追踪的场景优先选择Opaque Token(有状态管理)。
2.3 过期策略设计
过期策略直接影响系统安全性与用户体验。以下是三种常见策略及其适用场景:
| 策略 | Access Token有效期 | Refresh Token有效期 | 适用场景 |
|---|---|---|---|
| 短生命周期 | 5~15分钟 | 1~7天 | 金融、支付、高安全敏感系统 |
| 中等生命周期 | 30分钟~1小时 | 7~30天 | 企业SaaS、电商平台 |
| 长生命周期(受限) | 1~2小时 | 30~90天 | IoT设备、后台服务间通信 |
安全警告:Refresh Token有效期超过30天时,必须启用“令牌轮换”(Token Rotation)机制——每次刷新时同时颁发新的Refresh Token并废弃旧令牌,防止长期令牌泄露导致权限永久丢失。
2.4 刷新机制与令牌轮换
刷新流程是令牌生命周期管理的核心交互环节。下图展示了标准刷新流程与令牌轮换的完整链路:
2.5 安全存储要求
令牌存储位置直接决定泄露风险等级。以下是不同客户端类型的安全存储规范:
| 客户端类型 | Access Token存储 | Refresh Token存储 | 安全说明 |
|---|---|---|---|
| 浏览器SPA | 内存变量(闭包/模块作用域) | HttpOnly + Secure + SameSite=Strict Cookie | 禁止localStorage/sessionStorage存储任何令牌 |
| 原生移动App | 内存或系统安全存储(iOS Keychain / Android EncryptedSharedPreferences) | 系统安全存储 + 设备绑定(Key Attestation) | 避免使用SharedPreferences明文存储 |
| 后端服务 | 进程内存或分布式缓存(Redis) | 加密持久化(AES-256-GCM) | 缓存设置TTL与令牌过期时间一致 |
| IoT/嵌入式设备 | 安全元件(SE)或TEE隔离区 | 安全元件 + 硬件唯一密钥加密 | 禁止明文写入Flash文件系统 |
工程实践:在SPA中,将Access Token保存在JavaScript闭包变量中,配合BFF(Backend for Frontend)模式,由后端代理完成令牌刷新,前端仅持有短期会话Cookie,从根本上消除XSS窃取令牌的风险。
2.6 令牌生命周期状态机
从颁发到废弃,令牌经历多个状态转换。下图展示了完整的生命周期状态机:
2.7 关键工程要点总结
- 令牌绑定:将Access Token与客户端ID、IP段或设备指纹绑定,降低令牌被盗用后的影响范围。
- 刷新窗口:在Access Token过期前5%~10%的时间窗口内,客户端应主动发起静默刷新,避免请求中断。
- 并发刷新保护:使用锁机制或去重队列,防止多个请求同时触发刷新导致令牌竞争条件。
- 审计日志:每次令牌颁发、刷新、撤销操作均记录日志,包含时间戳、客户端ID、用户标识、令牌指纹(JTI)。
- 定期轮换密钥:JWT签名密钥至少每90天轮换一次,旧密钥保留一个过期窗口用于验证未过期令牌。
常见陷阱:不要将Refresh Token设计为“永久有效”。即使业务需要长期登录,也应设置最大绝对过期时间(如90天),到期后要求用户重新认证。同时,禁止在URL参数中传递任何令牌,防止日志泄露和中间人攻击。