2. 授权码流程详解:授权请求、用户认证、授权码发放、令牌交换、令牌刷新、令牌撤销的完整生命周期

OIDC(OpenID Connect)授权码流程(Authorization Code Flow)是当前最安全、最常用的身份认证与授权协议之一。它通过前端通道与后端通道的分离,确保敏感令牌(如 Refresh Token)不会暴露在浏览器或移动端环境中。本章将拆解该流程的六个核心阶段,并分析每个阶段在异步任务编排中的关键优化点。

2.1 流程全景图

下图展示了授权码流程的完整生命周期,包含客户端、授权服务器、资源服务器以及用户代理之间的交互顺序。

OIDC 授权码流程完整生命周期 OIDC 授权码流程完整生命周期 用户代理 (浏览器/移动端) 客户端 (后端服务) 授权服务器 (OIDC Provider) 资源服务器 (API 服务) ① 授权请求 ② 用户认证 ③ 授权码发放 ④ 令牌交换 ⑤ 令牌刷新 ⑥ 令牌撤销 资源访问 返回资源 说明:步骤①-③通过前端通道完成;步骤④-⑥通过后端通道完成,令牌不暴露给用户代理

2.2 阶段一:授权请求(Authorization Request)

客户端构造授权请求 URL,将用户代理重定向至授权服务器的 /authorize 端点。此阶段的关键参数如下:

参数 必选 说明
response_type 是 固定为 code,表示使用授权码流程
client_id 是 客户端在授权服务器注册时获得的标识
redirect_uri 是 授权码回调地址,必须与注册时一致
scope 是 请求的权限范围,如 openid profile email
state 强烈推荐 防 CSRF 攻击的随机字符串,回调时需校验
nonce 推荐 防重放攻击的随机值,会嵌入 ID Token
异步优化要点:在异步任务编排中,授权请求的构造和重定向可以封装为独立的异步函数。使用 state 参数作为回调任务的关联 ID,便于在回调到达时快速匹配对应的异步上下文。

2.3 阶段二:用户认证(User Authentication)

用户代理到达授权服务器后,服务器根据用户当前的会话状态决定认证方式:

  • 已登录会话:直接跳过认证页面,进入授权确认步骤。
  • 未登录会话:展示登录表单,要求用户提供凭证(用户名/密码、MFA 等)。
  • 会话过期:重新认证,同时保留原始授权请求参数。

认证完成后,授权服务器会生成一个临时的 授权码(Authorization Code),并绑定以下信息:

  • 客户端 ID(client_id)
  • 重定向 URI(redirect_uri)
  • 请求的 scope 列表
  • 认证用户标识(sub)
  • 过期时间(通常为 1-10 分钟)
  • 一次性使用标记

2.4 阶段三:授权码发放(Authorization Code Issuance)

授权服务器通过 302 重定向将用户代理送回客户端注册的 redirect_uri,并在 URL 查询参数中附带授权码:

HTTP/1.1 302 Found
Location: https://client.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=xyzABC123

客户端收到回调后,必须立即执行以下校验:

  1. 验证 state 值与发起请求时保存的值一致。
  2. 确认 redirect_uri 与注册值匹配。
  3. 记录授权码,准备发起令牌交换请求。
安全警告:授权码必须一次性使用。如果令牌交换失败或返回错误,客户端不应重试同一授权码,而应重新发起完整的授权流程。授权码泄露可能导致会话劫持。

2.5 阶段四:令牌交换(Token Exchange)

客户端使用授权码向授权服务器的 /token 端点发起 POST 请求,换取 Access Token、ID Token 和可选的 Refresh Token。此阶段必须通过后端通道完成,避免令牌暴露给用户代理。

令牌交换请求示例:

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://client.example.com/callback
&client_id=my-client-id
&client_secret=my-client-secret

授权服务器返回的响应:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "def50200d4b7f..."
}
返回字段 说明 生命周期
access_token 用于访问资源服务器的令牌 通常 1 小时
id_token JWT 格式的用户身份声明 与 access_token 一致
refresh_token 用于获取新 access_token 的长期令牌 数天至数月

2.6 阶段五:令牌刷新(Token Refresh)

当 Access Token 过期时,客户端可以使用 Refresh Token 获取新的 Access Token,无需用户重新认证。刷新请求同样通过后端通道完成:

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=def50200d4b7f...
&client_id=my-client-id
&client_secret=my-client-secret

刷新响应与令牌交换响应结构相同,但通常不包含新的 ID Token(除非 scope 包含 openid 且服务器配置为重新签发)。

异步编排关键点:在异步任务编排中,令牌刷新应设计为自动重试机制的一部分。当资源服务器返回 401 状态码时,异步任务应暂停当前操作,触发令牌刷新流程,刷新成功后自动重放原请求。建议使用指数退避策略避免频繁刷新。

2.7 阶段六:令牌撤销(Token Revocation)

当用户登出、客户端检测到安全事件或令牌泄露时,应主动撤销令牌。OIDC 提供 /revoke 端点用于撤销 Access Token 或 Refresh Token:

POST /revoke HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

token=eyJhbGciOiJSUzI1NiIs...
token_type_hint=access_token
&client_id=my-client-id
&client_secret=my-client-secret

撤销操作的注意事项:

  • 幂等性:撤销已过期或不存在的令牌应返回成功(HTTP 200),避免客户端因重复撤销而报错。
  • 级联撤销:撤销 Refresh Token 时,授权服务器应同时撤销由其派生的所有 Access Token。
  • 异步通知:在分布式系统中,令牌撤销事件应通过消息队列广播到所有服务实例,确保缓存中的令牌被及时清除。

2.8 生命周期状态机

下图展示了授权码流程中令牌的完整状态转换,帮助理解各阶段之间的依赖关系:

令牌生命周期状态机 令牌生命周期状态机 授权码 (Code) 活跃 (Active) 过期 (Expired) 已撤销 (Revoked) 令牌交换 超时 撤销请求 撤销过期令牌 令牌刷新 说明:令牌刷新仅适用于活跃状态;撤销操作对过期令牌同样有效(幂等)

2.9 异步编排中的生命周期管理策略

在异步任务编排中,令牌生命周期管理需要关注以下策略:

阶段 异步策略 关键指标
授权请求 使用异步任务生成并存储 state 和 nonce 回调匹配成功率 > 99.9%
令牌交换 封装为可重试的异步 RPC 调用,设置超时和重试次数 交换延迟 < 500ms (P99)
令牌刷新 预刷新机制:在令牌过期前 10% 时间发起异步刷新 刷新成功率 > 99.5%
令牌撤销 通过消息队列广播撤销事件,异步清除分布式缓存 撤销传播延迟 < 1s
最佳实践:建议将令牌生命周期管理封装为独立的 TokenManager 服务,对外提供统一的异步接口(如 getValidToken()、refreshToken()、revokeToken())。内部使用状态机和定时器自动处理令牌过期和刷新逻辑,上层业务无需关心令牌的具体状态。