核心做法:用短时有效的 access token 配合长期 refresh token,通过授权码(含 PKCE)或客户端凭证获取;传输全程走 TLS,存储优先 HttpOnly+Secure+SameSite 的 Cookie 或系统密钥库;失效用客户端自动刷新、服务器端撤销(黑名单/Introspection)、签名密钥轮换与重放防护共同保障;出海场景还要考虑时钟偏差、网络抖动与合规要求。

先把概念说清楚(像跟朋友解释)
想象令牌是门禁卡:access token 就像一张短期访客卡,能开门但有效期短;refresh token 更像前台签发的长期通行证,只能去前台换临时卡。服务端会核查门禁卡是否合法(签名/状态),客户端负责妥善保管,不让别人偷走。
Access Token(访问令牌)
- 用途:携带用户或客户端授权,供资源服务器(API)验证。
- 特性:通常短期(分钟到小时)、可以是 JWT(自包含)或随机字符串(需 introspection)。
Refresh Token(刷新令牌)
- 用途:在 access token 过期后,用来向授权服务器换取新的 access token。
- 特性:寿命长、比 access token 更敏感,必须严格保护和最小化暴露。
常见的获取流程(按场景)
1. 授权码 + PKCE(推荐用于 SPA 与移动端)
为什么用 PKCE?因为浏览器或移动端不能安全存放客户端密钥,PKCE 能防止授权码拦截。流程简化为:
- 客户端生成 code_verifier 和 code_challenge;
- 用户在授权页面登录并授权,授权服务器返回授权码(code);
- 客户端用授权码 + code_verifier 向 Token Endpoint 兑换 access token + refresh token;
- 后续调用用 access token,access token 过期时用 refresh token 续期。
2. 客户端凭证(Machine-to-Machine)
适用于后端到后端:客户端用 client_id+client_secret(或 mTLS)直接换取 access token。通常没有 refresh token,而是重新请求新的 access token 或用短时证书。
3. 密码模式(不推荐)
用户直接提交用户名和密码换 token,风险高,只在受信任环境并且确有必要时慎用。
传输与存储的最佳实践
存错了就像把钥匙挂在门外——后果明显。这里讲具体可执行的做法。
传输
- 强制 HTTPS(TLS)和 HSTS;
- 响应中不要在 URL 中返回 token(避免日志泄露);
- 避免通过 Referer 泄露;在跨域场景注意 CORS 配置。
存储
- Web(浏览器):优先使用 HttpOnly + Secure + SameSite=strict/ lax 的 Cookie 存放 refresh token;access token 可放内存或短时 Cookie,避免 localStorage(易 XSS 被窃)。
- SPA:access token 存内存(刷新或刷新后重新加载),refresh token 用 HttpOnly Cookie 或把刷新逻辑放在后端代理。
- 移动:使用 Keychain(iOS)/Keystore(Android);不要写入可备份的明文文件。
- 服务端:可以把 refresh token 或 session 存在数据库或缓存(Redis),并加密敏感字段。
令牌失效的多层策略(核心要点)
单靠一个机制往往不够,组合才可靠。
1. 短期 access token + refresh token
- access token 过期后客户端自动发起 refresh 请求;
- refresh token 也应有到期时间;如遇异常则要求用户重新登录。
2. Refresh token rotation(旋转)
每次用 refresh token 换新的 access/refresh 时,让授权服务器返回新的 refresh token,并立即使旧的失效。这样即便 refresh token 被窃取,攻击者只能使用一次。
3. Token 撤销 / 黑名单
当用户登出、修改密码、管理员撤销访问时,服务器应能使当前 token 立即失效。实现方法:
- 维护一个撤销列表(黑名单)或 token 的状态表;
- 对于 JWT,可以在验证时查询撤销表或使用较短的 JWT 有效期并配合 key rotation;
- 提供 /revoke 或 /introspect 接口,供资源服务器或其他服务验证。
4. 签名密钥轮换(Key Rotation)
定期替换签名密钥,并支持多密钥并存以兼容旧 token 的短期验证。配合密钥发布(JWKS),可以保证被盗签名密钥的影响可控。
5. 防重放和绑定
- 绑定客户端信息(如 TLS 客户端证书、device fingerprint 或同域 cookie)来减少 refresh token 被滥用的风险;
- 使用 nonce、jti 等字段避免 token 重放。
错误与异常处理表(实用对照)
| 错误 | 含义 | 客户端处理 |
| 401 / invalid_token | access token 无效或过期 | 尝试使用 refresh token 刷新;刷新失败则引导登录 |
| 403 / insufficient_scope | 权限不足 | 提示用户授权不足或请求更高 scope 的授权流程 |
| 400 / invalid_grant | 授权码无效或 refresh token 被撤销 | 停止自动重试,提示重新登录 |
| 429 | 速率限制 | 指数退避并告知用户稍后重试 |
常见攻击场景与对应防护
- XSS:导致 token 泄露。防护:严格 CSP、输入校验、尽量不把 token 暴露给 JS(HttpOnly)。
- CSRF:伪造登录态的请求。防护:SameSite Cookie、CSRF Token、双重提交 Cookie。
- 刷新令牌滥用:被窃取后长期有效。防护:rotate refresh token、绑定客户端信息、限制刷新频率。
- MitM:拦截通信。防护:强制 HTTPS、证书固定(Optional)、HSTS。
出海(跨境)特别注意点
海外部署不仅是技术问题,还有合规、网络、用户体验等考量。
- 合规性:不同地区对个人数据和认证有不同要求(GDPR、PDPA 等),需审查 token 中是否携带个人信息,避免将 PII 写入 JWT 明文负载。
- 时区与时钟偏差:服务器和客户端时钟不一致会导致过期判断异常。通常允许 1–5 分钟的时钟偏差(skew)并记录同步失败事件。
- 网络质量:海外网络延迟或丢包更明显,refresh 流程应具备幂等和重试策略,UI 提示足够友好。
- 分区化密钥管理:跨区域部署时,考虑密钥在多个区域的分发、轮换与合规性审计。
- 本地化错误提示:不同语言的用户需要本地化的错误信息,避免直译带来的误导。
可操作的接入与失效处理清单(便于落地)
- 使用授权码 + PKCE 为首选登录方式;
- access token TTL:短(例如 5–15 分钟);refresh token TTL:按业务定(7–90 天)并支持 rotation;
- HttpOnly + Secure + SameSite Cookie 存 refresh token;access token 保存在内存或短时 cookie;
- 实现 refresh token rotation,并在服务器存储每次 rotation 的 jti 以防回放;
- 提供 /revoke 和 /introspect 接口;资源服务器应对接 introspection 或本地验证 + 撤销查询;
- 密钥轮换策略:提前发布新公钥并在一定窗口内支持旧签名;
- 日志要记录事件(登录、刷新、撤销、token 验证失败),但避免把 token 原文写入日志;
- 监控:refresh 失败率、重复 refresh 次数、撤销事件激增需要告警。
建议的 TTL 参考(可按风险调整)
| 客户端类型 | access token | refresh token |
| 高风险(公开网页、第三方) | 5–15 分钟 | 7–14 天(必须 rotation) |
| 移动 App(受信任) | 15–60 分钟 | 30–90 天(rotation + binding) |
| Machine-to-Machine | 短到中(5–60 分钟)或使用短期证书 | 通常不发 refresh,使用客户端凭证或 mTLS |
监控与指标(哪些东西要看)
- Token 颁发/刷新次数与成功率;
- Refresh token 被拒绝/失效事件;
- 登录失败、撤销请求频率;
- 每个客户端或 IP 的刷新速率异常检测;
- 密钥轮换失败率和密钥有效性覆盖率。
典型刷新交互示例(简化 HTTP)
这里把步骤写得像手把手教你,方便直接复制到自己文档里。
POST /oauth/token Host: auth.example.com Content-Type: application/x-www-form-urlencodedgrant_type=refresh_token &refresh_token=eyJ...OLD_REFRESH... &client_id=abc123
响应(成功): 200 OK { "access_token": "eyJ...NEW_ACCESS...", "token_type": "Bearer", "expires_in": 900, "refresh_token": "eyJ...NEW_REFRESH..." }
注意:授权服务器在返回新的 refresh_token 时需把旧的立即标记为无效(服务端状态),并记录 jti 用于审计。
实现中的小细节(那些容易被忽略的)
- 不要把 token 放在日志、异常堆栈或第三方监控事件中;
- 刷新过程中并发控制:当多个请求同时发现 token 过期,应该让第一个触发刷新,其他请求等待或重试,避免重复刷新导致旋转冲突;
- Grace period:在密钥轮换或临时网络抖动时,允许短暂的宽限以减少用户感知的登录中断;
- 尽量让错误消息对用户友好且本地化,但对技术细节(比如 token 原文)保持模糊;
- 对重要操作(修改密码、支付)可以要求再认证或短时增强认证(2FA)。
好了,这些是我在实战里常用并反复迭代出的准则:把 access token 当成短期钥匙,把 refresh token 当成前台签证,并用旋转、撤销、密钥轮换与绑定等多层保护手段来构建可信任的失效处理机制。出海时别忘了把法律和网络差异也算进来,做到既安全又用户友好。听起来多步骤,但按清单一步步来就行,有问题我们可以再把某一项拆出来细说。