Token、JWT 与刷新撤销
Token 是凭据或授权结果的载体,不规定其内部格式。随机字符串可以引用服务端记录,JWT 则携带结构化声明;选择格式之前,需要先确定谁签发、谁接受、可以访问什么,以及何时停止接受。
区分令牌用途与格式
| 令牌 | 接收方与用途 | 主要边界 |
|---|---|---|
| Access Token | 资源服务器,访问指定 API | 受众、权限范围、有效期 |
| Refresh Token | 授权服务器,申请新令牌 | 客户端、授权范围、刷新链与撤销 |
| ID Token | OIDC 客户端,确认认证结果 | 客户端受众、认证事务与身份 |
| 恢复令牌 | 账号恢复入口,完成特定动作 | 单次、用途、账号与短有效期 |
ID Token 不作为通用 API Access Token,Refresh Token 不发给业务 API。API 不应因为某段字符串可以解码成 JWT,就认为它适用于当前接口。
随机不透明令牌便于集中检查活动状态,代价是查询。JWT 可由 API 本地验证,代价是负载更大、密钥管理与状态滞后。OAuth introspection 与撤销分别由 RFC 7662和 RFC 7009定义,令牌是否自包含并不决定它必须采用哪种查询方式。
JWT 的签名与声明
常见签名 JWT 使用三个以点号分隔的部分:JOSE Header、Claims、签名或 MAC。Base64url 编码不是加密,普通签名 JWT 的内容可被持有者读取。RFC 7519也允许使用 JWE 保护机密性,不能把“JWT”一概等同于三段式明文负载。
以下仅是 Access Token 的示意声明,不是已签发的可用令牌:
{
"iss": "https://identity.example.com",
"sub": "user_123",
"aud": "orders-api",
"client_id": "mobile-app",
"scope": "orders:read",
"iat": 1793318400,
"exp": 1793319000,
"jti": "token_7d6f"
}exp 和 iat 使用 Unix 秒数;示例给出 10 分钟有效期,只用于说明时间关系。不要混用毫秒,也不要默认所有声明都由任意 JWT 标准强制要求。应用应选择并固定验证配置;采用 OAuth JWT Access Token profile 时,按 RFC 9068处理其必需声明与 typ。
验证顺序
依据 RFC 8725,验证器需要固定可接受算法、密钥来源、签发者、受众和令牌用途,防止算法替换与跨用途混用。
工程上可按下列边界配置成熟库:
- 限制输入长度与结构,不允许无界嵌套或超大声明。
- 使用配置的算法集合和签发者对应密钥验证密码学保护。
- 校验
iss、aud、exp,并按选定 profile 检查其他必需声明。 - 处理
nbf、允许的时钟偏差与令牌类型,拒绝不符合当前用途的令牌。 - 将已验证主体交给业务授权,检查账号、成员和对象状态。
Header 中的 kid 是密钥选择提示,不是信任依据;不根据任意 jku、x5u 或 kid 去访问攻击者指定的 URL、文件或数据库表达式。共享 HMAC 密钥意味着每个验证方也具备签发能力;多个独立 API 场景通常更适合由签发方保管私钥、验证方持有公钥。
Refresh Token 轮换
RFC 9700 §4.14要求公共客户端的 Refresh Token 使用发送方约束或轮换等规定的保护方式。轮换时每次刷新得到新 Refresh Token,旧值再次出现可作为重放信号。
建议每台设备或每次授权使用独立刷新族,记录 family_id、令牌摘要、账号、客户端、状态、父子关系和绝对期限。下图是一种事务设计,不是标准指定的表结构;它选择在重用时撤销刷新族。
令牌记录和刷新族的锁定或条件更新需要遵守一致的并发协议,防止刷新与撤销同时创建出可用后继。图中的数据库只保存令牌摘要,R1、R2 的原始值不进入普通日志。
数据库无法分辨重用一定来自攻击者。两个标签页同时刷新、网络重试或客户端丢失响应,也可能触发同一现象。
并发刷新与响应丢失
客户端应采用单次在途刷新:同一设备同一时刻只有一个刷新请求,其他请求等待结果。浏览器多标签页还需要跨页协调,不能只在每个页面各设一把内存锁。
如果事务已提交但响应丢失,客户端只持有旧令牌。可选择要求重新登录,或设计严格绑定客户端与幂等请求的短期结果重放机制。后者需要受保护地保留可重发结果,增加秘密存储与泄露面;不能用无限接受旧 Refresh Token 来解决。
删除所有 used 记录会丢失重用检测证据,应按刷新族有效期与审计需求清理。Refresh Token 原始值也不能写入普通数据库日志、链路追踪或错误上报。
撤销与权限变更
| 机制 | 生效方式 | 代价 |
|---|---|---|
| 短期 Access Token | 等待过期 | 有明确残余窗口 |
| 在线 introspection | API 查询当前状态 | 可用性、延迟与缓存一致性 |
| 令牌或会话撤销列表 | 查询 jti 或登录实例 | 存储与查询负担 |
| 账号安全版本 | 比较当前版本 | 需获得足够新的版本状态 |
| 撤销 Refresh Token | 阻止继续刷新 | 已有 Access Token 通常仍需额外处理 |
权限变化后,即使旧 Access Token 仍有效,资源服务也可以重新查询成员和对象权限。不能把“签名有效”和“当前业务允许”合并成一个判断。
密钥轮换与泄露
正常轮换先发布新公钥,让验证方有机会获取,再切换签名私钥;保留旧公钥直到旧令牌及允许的时钟偏差窗口结束。验证方对未知 kid 的刷新要限频,防止攻击者触发无界远程请求。
私钥泄露需要紧急拒绝旧密钥,不能机械等待自然过期。对于不再查询签发方的 API,必须有密钥失效传播与缓存刷新机制。JWKS 拉取失败时可按明确策略使用尚可信的缓存,未知密钥不能降级跳过签名。