令牌生命周期怎么设计
OAuth 类体系通常采用两种令牌搭配:访问令牌有效期短(分钟级),用于携带权限调用接口,泄露窗口小;刷新令牌有效期长,用于在访问令牌过期后换新。关键设计点是刷新令牌一次性使用并在使用后轮换——旧刷新令牌立即失效,如果它再次出现,说明已经泄露,应该吊销整个会话并告警。客户端要能区分“过期需要刷新”和“凭证失效需要重新登录”两种情况。
基本信息
| 访问令牌 | 分钟级有效期,减少泄露窗口 |
|---|---|
| 刷新令牌 | 长期有效,使用后轮换并检测重放 |
| 处理 | 过期返回 401 并提示刷新;刷新失败引导重新认证 |
| 时钟容忍 | 校验时留出少量时间偏差容忍窗口,避免边界请求被误判过期 |
典型使用场景
- 移动端与单页应用:用短期访问令牌加刷新令牌,避免频繁登录
- 第三方接入:为每个授权关系维护独立的刷新令牌与吊销状态
- 多端登录管理:按设备维护会话,支持单设备登出与全端登出
- 权限变更:调整用户权限后按会话吊销,避免旧令牌继续持有高权限
- 风控场景:检测到异常登录后立即吊销相关刷新令牌
刷新流程(伪代码)
刷新流程(伪代码)
on401(response):
if (refreshToken 有效):
新令牌对 = refresh(refreshToken) // 旧刷新令牌立即失效
重放原请求一次
else:
清除会话 → 引导重新登录状态与处理
| 状态 | 判定 | 客户端动作 |
|---|---|---|
| 访问令牌过期 | 401 且错误码表示令牌过期 | 静默刷新后重放一次原请求 |
| 刷新令牌过期 | 刷新接口返回无效授权 | 清除会话并引导重新登录 |
| 刷新令牌被重放 | 服务端检测到已轮换的旧令牌 | 吊销该会话全部令牌并告警 |
| 权限不足 | 403 而非 401 | 不做刷新,提示权限问题 |
| 并发刷新冲突 | 多个请求同时触发刷新 | 客户端单飞:只发起一次刷新,其余请求等待结果 |
使用要点
- 刷新令牌轮换配合重放检测:旧令牌再次出现时按泄露处置,吊销会话并告警
- 客户端区分 401 与 403:前者可能通过刷新恢复,后者刷新无效只会浪费请求
- 并发请求要单飞刷新:多个请求同时刷新会互相作废令牌,形成刷新风暴
- 过期时间留容忍窗口:客户端与服务端时钟偏差会导致边界请求被误判,判断提前量通常取数十秒
- 会话与设备绑定要可审计:能列出并单独吊销某一设备的会话
刷新令牌轮换配合重放检测:旧令牌再次出现时按泄露处置,吊销会话并告警,而不是简单返回错误,否则泄露无法被发现。
把刷新令牌存进浏览器可被脚本读取的位置(如普通 localStorage)会放大 XSS 影响:存储位置与传输方式要与整体前端安全方案一起决定。