Access Token 过期怎么续命?手把手教你用 refresh_token 换发新令牌

刷新令牌通过过期换发机制维持会话,并在安全轮换策略下实现长期访问凭证的自动更新与失效控制。

OAuth 2.0 中刷新令牌的核心机制与使用场景

OAuth 2.0 利用刷新令牌在访问令牌过期时无需重新登录即可续期,适用于需要保持用户长期在线的服务场景。

为什么 Access Token 过期后还需要 Refresh Token?

OAuth 2.0 的设计重心并非为每个请求重新计算签名,而是建立资源所有者、客户端与授权服务器之间的委托关系 [1]。它解决的是“谁代表谁访问、权限持续多久”的问题,这与 HMAC 这种针对单个请求的完整性保护有本质区别 [2][1]

Access Token 天生短命,一旦过期,用户就必须重新登录。Refresh Token Grant 是解决“持续访问”的标准方案 [3]。当你拿到 Refresh Token,就能在不打扰用户的情况下,向身份端点申请新的 Access Token [3]

区分 Client Credentials flow 和 Refresh Token flow 很关键:

  • Client Credentials:客户端仅能访问属于它自己的资源,不涉及具体用户身份。
  • Refresh Token Flow:专门用于代表特定用户进行长期访问,是处理用户会话延续的唯一标准路径。

在 Client Credentials 流程中,Ory 文档展示了提交 client_idclient_secret 获取令牌的常见做法,认证方式多为 client_secret_basic[4]。但这只是其中一种实现,不能默认所有服务都支持 HTTP Basic Authentication。你必须查阅具体服务商的规范来确认可用的认证方式 [4]

信任链条与权限边界

理解 Refresh Token 的作用,先要理清信任链条。资源所有者(用户)将权限委托给客户端,授权服务器负责颁发凭证。Access Token 像一张限时入场券,而 Refresh Token 则是后台的续期卡。

Apaleo 文档演示了向身份端点提交 grant_type=refresh_token 换取新令牌的逻辑 [3]。这个例子展示了令牌生命周期的一种实现,但材料并未覆盖轮换、撤销或错误码处理细节。你不能直接把 Apaleo 的参数当作通用标准,实际开发必须核对目标服务的详细规范 [3]

很多开发者容易忽略的一个致命陷阱是:误以为 Refresh Token 可以无限次复用而不受限制。事实上,如果服务端开启了“轮换策略”,你每次用旧令牌换来的新 Access Token 时,旧的 Refresh Token 就会立即失效并被替换为新令牌。如果你没有做好“捕获并存储新 Refresh Token”的逻辑,下一次尝试刷新时就会收到 invalid_grant 错误,导致用户会话意外中断。因此,在代码层面,必须确保每次成功响应后,立即更新本地存储的 Refresh Token,而不是保留旧值。

本章执行检查清单

  • [ ] 确认业务场景是否涉及用户身份委托(非纯机器对机器)
  • [ ] 明确 Access Token 的短时效特性是引入 Refresh Token 的根本原因
  • [ ] 区分当前流程是代表客户端自身还是代表用户
  • [ ] 查阅目标服务商文档,确认具体的客户端认证方式(非盲目套用 Basic Auth)
  • [ ] 确认本地代码逻辑已包含“接收新 Refresh Token 并覆盖旧值”的步骤

实战演示:如何通过 grant_type=refresh_token 换取新令牌

系统通过向身份端点提交 grant_type=refresh_token 参数及有效令牌,直接换取新的访问凭证以延续服务连接。

Access Token 过期后,系统不会让你重新登录,而是直接调用 Refresh Token Grant 机制来续命。你只需要向身份端点提交特定的参数,就能换回新的访问凭证 [3]。这一步是维持长期在线服务的关键,但操作逻辑必须严格遵循规范。

向身份端点发起请求的标准步骤

拿到 Refresh Token 后,你需要构造一个 POST 请求发给授权服务器的令牌端点。请求体中必须包含三个核心字段:grant_type 固定为 refresh_token,填入你手里持有的 refresh_token 字符串,以及代表应用身份的 client_id[3]。这三项缺一不可,少了任何一项都会导致验证失败。

除了请求体参数,客户端认证同样重要。大多数服务(如 Ory)要求使用 client_secret_basic 方式,即在 HTTP Header 中使用 Basic Authentication 传递 client_idclient_secret[4]。这就像你在柜台办事不仅要出示工牌(client_id),还得核对暗号(client_secret)。不同服务商对认证方式的要求存在差异,有的可能支持表单提交或自定义头,你必须查阅具体文档确认,不能想当然地套用标准 [4]

请求发出后,如果一切正常,服务器会返回 JSON 格式的新 Access Token。这个新令牌通常包含 access_tokentoken_type(如 Bearer)、expires_in 等字段。拿到它后,立即替换掉本地缓存的旧令牌即可继续业务逻辑 [3]

注意:整个流程仅在你已经通过授权码或其他方式获取过 Refresh Token 的前提下有效。如果你从未获得过该令牌,或者令牌已被撤销,这里的所有步骤都将失效 [3]。不要试图用这个接口去“生成”初始令牌,它只负责“刷新”。

为了更直观地对比不同场景下的响应差异,我们可以参考 Google 和 Microsoft 的常见实践。Google 在返回新 Access Token 的同时,通常会附带一个新的 Refresh Token(如果启用了轮换),并且其响应中会包含 scope 字段以确认权限范围未变;而 Microsoft Entra ID (Azure AD) 在某些配置下,如果请求的 scope 小于原始授权 scope,可能会拒绝刷新或仅返回部分权限。这些细微差别意味着你的解析逻辑不能写死,需要能够动态处理响应中的额外字段。

关键检查清单

  • [ ] 确认本地已存储有效的 Refresh Token
  • [ ] 请求 URL 指向正确的身份端点(非授权端点)
  • [ ] Body 中包含 grant_type=refresh_token 和真实的 refresh_token
  • [ ] 携带了符合服务端要求的客户端认证信息(如 Basic Auth)
  • [ ] 解析响应并提取新的 access_token 存入缓存
  • [ ] 强制动作:若响应中包含 refresh_token,立即覆盖本地旧值

开发必读:Refresh Token 的轮换、撤销与错误处理

开发必须依据特定服务商规范处理刷新令牌的轮换、撤销及错误码,仅复制示例参数无法保障系统安全与功能稳定。

别以为把 Apaleo 文档里的参数抄进代码就能高枕无忧。现有材料明确指出,该示例未充分核验 refresh token 轮换、撤销、过期、错误码和客户端认证要求 [3]。这意味着你直接照搬端点参数,可能埋下安全漏洞或导致功能失效。实际开发中,你必须查阅特定服务商规范来确认细节。

为什么不能直接照搬文档中的端点参数?

OAuth 2.0 标准(RFC 6749)只定义了框架,具体实现千差万别 [1]。Apaleo 的例子展示了令牌生命周期的一种路径,但缺乏对关键安全机制的完整描述 [3]。如果你忽略“刷新令牌轮换”机制,攻击者截获一个旧的 refresh token 后,就能无限期地伪造身份发起重放攻击。不同授权服务器对令牌轮换策略的定义完全不同,有的强制每次换发都更新旧令牌,有的则允许复用。

此外,客户端认证方式也非铁律。虽然 Ory 文档展示了 client_secret_basic 这种常见做法,但这不代表所有服务都必须如此 [4]。有些厂商可能要求 mTLS 或其他自定义头。盲目套用通用参数,往往会在生产环境遇到鉴权失败。

遇到错误时如何排查 Refresh Token 问题?

当请求返回错误时,不要只看状态码,要深入解析错误类型。常见的两类故障场景截然不同:

  1. 客户端认证失败:通常是 client ID 或 secret 填错,或者签名算法不匹配。
  2. 令牌本身失效:涉及 refresh token 过期、被撤销或已轮换。

你需要建立一套基于错误码的调试思路。若收到 invalid_grant,说明令牌无效或被撤销;若收到 expired_token,则需立即启动重新登录流程。这些错误码的具体含义和触发条件,必须依据当前使用的授权服务器规范来解读 [3]

下表对比了两种典型错误场景的特征与应对动作,帮助你快速定位问题:

错误现象 典型错误码 根本原因 推荐应对动作
认证凭据错误 invalid_client Client ID/Secret 错误或格式不对 检查密钥配置,核对服务端要求
令牌已失效 expired_token Refresh Token 超过有效期 引导用户重新登录获取新凭证
令牌被回收 invalid_grant 令牌已被撤销或发生轮换 清除本地缓存,强制用户重登
权限不足 insufficient_scope 请求范围超出授权范围 调整 scope 或申请额外授权

记住,没有通用的“万能配置”。每个服务商对 grant_type=refresh_token 的支持细节都可能微调。在动手写代码前,花半小时阅读目标平台的最新安全规范,比事后排查 Bug 更高效。

特别建议在实际开发中增加一个重试机制:当遇到 invalid_grant 且无法确定是轮换还是撤销时,不要立即报错给用户,可以先尝试用当前的 Refresh Token 再试一次(防止网络抖动导致的重复请求),如果依然失败,再判定为彻底失效并引导重登。这种容错设计能显著降低因网络波动导致的用户投诉。


常见问题解答 (FAQ)

Q: 如果 Access Token 过期了,我可以直接调用刷新接口吗? A: 不行。刷新接口的前提是你必须已经拥有一个有效的 Refresh Token。如果 Refresh Token 也已过期或被撤销,你必须引导用户重新进行完整的授权流程(如重新登录)。

Q: grant_type=refresh_token 是所有 OAuth 服务商通用的吗? A: 虽然这是 RFC 6749 标准的定义,但在实际落地中,不同服务商(如 Google, Microsoft, 或私有部署的 Keycloak/Ory)可能在参数命名、认证方式(Basic Auth vs Form Data)或错误码定义上存在细微差异。务必以官方文档为准。

Q: 什么是 Refresh Token 轮换(Rotation)? A: 这是一种安全机制。当使用 Refresh Token 换取新的 Access Token 时,服务器不仅返回新令牌,还会同时作废当前的 Refresh Token 并颁发一个新的。这样可以防止旧令牌被窃取后长期使用。如果你的服务不支持轮换,安全风险会显著增加。


参考来源

  1. RFC 6749 - The OAuth 2.0 Authorization Framework · https://datatracker.ietf.org/doc/html/rfc6749(S级)
  2. http-hmac-spec/README.md at 2.0 · acquia/http-hmac-spec · https://github.com/acquia/http-hmac-spec/blob/2.0/README.md(A级)
  3. OAuth 2.0: Refresh token grant flow | Apaleo Developer Documentation · https://apaleo.dev/guides/oauth-connection/refresh-token.html(B级)
  4. OAuth2 client credentials flow | Ory · https://www.ory.com/docs/oauth2-oidc/client-credentials(B级)