2. OAuth2.0 授权码模式(Authorization Code):完整流程、授权码获取、令牌交换、安全考量

授权码模式是 OAuth2.0 中最完备、最安全的授权流程,适用于有服务端后端的 Web 应用。它通过引入一个临时的“授权码”作为中间凭证,确保访问令牌(Access Token)只在安全的服务端通道中传输,避免令牌在用户代理(浏览器)中暴露。

2.1 核心角色与交互概览

在授权码模式中,涉及四个核心角色:资源拥有者(用户)、用户代理(浏览器)、客户端(Web 应用)、授权服务器。整个流程通过两次重定向和一次后端请求完成令牌的获取。

关键原则: 授权码是一次性的、短时效的临时凭证,必须在客户端与授权服务器之间的安全后端通道(TLS)中交换令牌。浏览器仅接触授权码,不接触访问令牌。
授权码模式完整流程 授权码模式完整流程 资源拥有者 用户代理 客户端 授权服务器 ① 访问资源 ② 构造授权请求 ③ 302重定向 ④ 用户认证与授权 ⑤ 返回授权码 ⑥ 后端交换令牌 ⑦ 返回访问令牌 ⑧ 返回资源 安全要点 • 授权码仅一次有效 • 令牌在后端通道传输 • 必须使用 TLS 加密

2.2 完整流程步骤分解

授权码模式包含以下七个核心步骤,每一步都涉及特定的 HTTP 请求和参数传递。

步骤 发起方 动作 关键参数
1 用户 访问客户端应用 —
2 客户端 构造授权请求 URL,重定向用户到授权服务器 response_type=code, client_id, redirect_uri, scope, state
3 授权服务器 展示登录页面,用户认证并授权 —
4 授权服务器 生成授权码,通过 302 重定向返回给客户端 code, state
5 客户端 使用授权码,通过后端通道向授权服务器请求令牌 grant_type=authorization_code, code, redirect_uri, client_id, client_secret
6 授权服务器 验证授权码和客户端凭证,返回访问令牌和刷新令牌 access_token, token_type, expires_in, refresh_token
7 客户端 使用访问令牌请求受保护资源 Authorization: Bearer <token>

2.3 授权码获取:请求与响应详解

授权码获取阶段的核心是客户端构造一个授权请求 URL,引导用户访问授权服务器的 /authorize 端点。

2.3.1 授权请求(客户端 → 授权服务器)

GET /authorize?
  response_type=code
  &client_id=web_app_001
  &redirect_uri=https://client.example.com/callback
  &scope=openid%20profile%20email
  &state=xyz123abc
  &nonce=abc456def
HTTP/1.1
Host: auth.example.com
参数说明:
  • response_type=code:固定值,标识使用授权码模式。
  • state:客户端生成的随机字符串,用于防止 CSRF 攻击。授权服务器在回调中会原样返回。
  • redirect_uri:必须与客户端注册时登记的 URI 完全一致,防止开放重定向攻击。

2.3.2 授权响应(授权服务器 → 客户端)

用户认证并授权后,授权服务器通过 302 重定向将授权码返回给客户端。

HTTP/1.1 302 Found
Location: https://client.example.com/callback?
  code=8xLOxBtZp8
  &state=xyz123abc
安全验证: 客户端收到回调后,必须立即验证 state 参数是否与之前发送的一致。如果不一致,应立即拒绝该请求,防止 CSRF 攻击。

2.4 令牌交换:后端通道的安全保障

令牌交换是授权码模式的安全核心。客户端使用授权码,通过安全的服务端到服务端通道(后端通道)向授权服务器的 /token 端点发起请求。

2.4.1 令牌请求(客户端 → 授权服务器)

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

grant_type=authorization_code
&code=8xLOxBtZp8
&redirect_uri=https://client.example.com/callback
关键安全机制:
  • 客户端认证: 使用 client_id 和 client_secret 通过 HTTP Basic Auth 或 POST 参数进行认证。
  • 授权码一次性: 授权服务器在验证成功后必须立即标记该授权码为已使用,防止重放攻击。
  • redirect_uri 验证: 必须与授权请求中的 redirect_uri 一致,防止授权码被劫持。

2.4.2 令牌响应(授权服务器 → 客户端)

HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rT4nX8kL2pQ9mZ6v",
  "scope": "openid profile email"
}

2.5 安全考量与最佳实践

授权码模式虽然安全,但实现不当仍会引入漏洞。以下是关键安全考量和对应的最佳实践。

安全风险 描述 最佳实践
CSRF 攻击 攻击者伪造授权回调,窃取用户授权 使用 state 参数,验证其值与会话绑定
授权码拦截 授权码在浏览器中暴露,被恶意脚本窃取 使用 PKCE(Proof Key for Code Exchange)增强安全
重放攻击 授权码被多次使用 授权码一次性使用,服务器端记录已用授权码
开放重定向 攻击者利用 redirect_uri 跳转到恶意站点 严格校验 redirect_uri 与注册 URI 完全匹配
令牌泄露 访问令牌在传输或存储中被窃取 全程使用 TLS,令牌不在浏览器中持久化存储
客户端凭证泄露 client_secret 被泄露 使用环境变量管理密钥,定期轮换
授权码模式安全架构 授权码模式安全架构 安全边界 用户代理(浏览器) • 仅接触授权码 • 不接触访问令牌 • 受同源策略保护 • 使用 state 防 CSRF 客户端(后端) • 存储 client_secret • 后端通道交换令牌 • 验证 state 参数 • 安全存储令牌 授权服务器 • 验证授权码一次性 • 验证 redirect_uri • 验证客户端凭证 • 签发访问令牌 TLS TLS PKCE 增强 code_challenge + code_verifier 令牌安全 短期有效 + 刷新令牌轮换 审计日志 记录所有授权和令牌请求

2.6 PKCE 扩展:移动端与单页应用的安全增强

对于无法安全存储 client_secret 的客户端(如移动应用、单页应用),OAuth2.0 提供了 PKCE(Proof Key for Code Exchange)扩展。PKCE 通过动态生成的密钥对,即使授权码被拦截,也无法交换令牌。

PKCE 核心流程:
  1. 客户端生成一个随机的 code_verifier(43-128 个字符)。
  2. 客户端计算 code_challenge = SHA256(code_verifier),并 Base64 URL 编码。
  3. 授权请求中携带 code_challenge 和 code_challenge_method=S256。
  4. 令牌请求中携带 code_verifier,授权服务器验证其与 code_challenge 匹配。

2.7 常见调试问题与排查

问题现象 可能原因 排查方法
授权码返回后,令牌交换失败 授权码已过期或已被使用 检查授权码有效期(通常 10 分钟),确认未重复使用
redirect_uri 不匹配 回调 URI 与注册 URI 不完全一致 检查 URI 的协议、域名、端口、路径是否完全一致
state 验证失败 CSRF 攻击或会话丢失 确认 state 值在会话中正确存储和比对
令牌返回后无法访问资源 令牌作用域不足或令牌已过期 检查 scope 参数,确认令牌未过期
PKCE 验证失败 code_verifier 与 code_challenge 不匹配 确认 code_challenge_method 为 S256,重新计算哈希
调试建议: 在开发环境中,使用 OAuth2.0 调试工具(如 OAuth2.0 Playground)逐步验证每个步骤的请求和响应。开启授权服务器的详细日志,记录所有授权码和令牌请求的完整参数。使用 Mock 服务隔离外部依赖,确保调试环境可控。