2. 授权码模式详解:授权码请求、用户授权、授权码发放、令牌交换、刷新令牌、完整序列图
授权码模式(Authorization Code Grant)是 OAuth2 中最安全、最常用的流程,特别适用于有后端服务器的 Web 应用。其核心在于通过一个临时的“授权码”作为中间凭证,避免访问令牌直接暴露在用户代理(浏览器)中。本节将拆解该模式的五个核心阶段,并给出完整的序列图。
2.1 核心参与方与角色
| 角色 | 说明 | 典型示例 |
|---|---|---|
| 资源所有者 | 拥有受保护资源的用户 | 最终用户 |
| 客户端 | 需要访问资源的第三方应用 | Web 后端服务 |
| 授权服务器 | 负责认证用户并发放令牌 | Auth0、Keycloak |
| 资源服务器 | 托管受保护资源的 API | REST API 服务 |
2.2 阶段一:授权码请求
客户端引导资源所有者访问授权服务器的 /authorize 端点。请求通常通过浏览器重定向发起,必须包含以下参数:
- response_type:固定值
code,表示请求授权码。 - client_id:客户端在授权服务器注册时获得的唯一标识。
- redirect_uri:授权成功后,授权服务器将用户重定向回客户端的地址。
- scope:请求的权限范围(如
openid profile email)。 - state:客户端生成的随机字符串,用于防止 CSRF 攻击。
安全要点:state 参数是授权码模式中防止跨站请求伪造(CSRF)的关键。客户端必须验证回调中的 state 值与发起请求时保存的值一致。
GET /authorize?
response_type=code&
client_id=web_app_001&
redirect_uri=https://client.example.com/callback&
scope=openid%20profile&
state=xyzABC123
HTTP/1.1
Host: auth.example.com
2.3 阶段二:用户授权
授权服务器收到请求后,首先验证客户端的身份(通过 client_id)和 redirect_uri 的合法性。然后,如果用户尚未登录,授权服务器会展示登录页面;用户登录后,授权服务器会展示授权页面,告知用户客户端请求的权限范围。
- 用户确认授权后,授权服务器生成一个临时的授权码。
- 授权码与客户端标识、重定向 URI、授权范围绑定。
- 授权码通常具有较短的有效期(如 5-10 分钟),且只能使用一次。
最佳实践:授权码应具有高熵值(至少 128 位随机数),且必须在服务端生成。禁止使用可预测的序列号或时间戳作为授权码。
2.4 阶段三:授权码发放
用户授权后,授权服务器通过浏览器将用户重定向回客户端指定的 redirect_uri,并在 URL 查询参数中附上授权码和原始的 state 值:
HTTP/1.1 302 Found
Location: https://client.example.com/callback?
code=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&
state=xyzABC123
客户端收到回调后,必须立即验证 state 值是否与之前发送的一致。验证通过后,客户端提取授权码,准备进行令牌交换。
2.5 阶段四:令牌交换
客户端使用授权码,通过后端通道(服务器到服务器)向授权服务器的 /token 端点发起 POST 请求,换取访问令牌和可选的刷新令牌。此请求必须包含客户端认证信息。
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code&
code=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&
redirect_uri=https://client.example.com/callback
| 参数 | 说明 |
|---|---|
| grant_type | 固定值 authorization_code |
| code | 上一步获取的授权码 |
| redirect_uri | 必须与授权请求中的 redirect_uri 一致 |
| client_id / client_secret | 客户端凭证,用于认证客户端身份 |
授权服务器验证授权码、客户端身份和重定向 URI 后,返回令牌响应:
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200a1b2c3d4e5f6...",
"scope": "openid profile"
}
2.6 阶段五:刷新令牌
访问令牌过期后,客户端可以使用刷新令牌获取新的访问令牌,而无需用户再次授权。刷新令牌的请求同样通过后端通道发起:
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=refresh_token&
refresh_token=def50200a1b2c3d4e5f6...&
scope=openid%20profile
安全警告:刷新令牌是长期凭证,必须安全存储。如果刷新令牌泄露,攻击者可以持续获取新的访问令牌。建议对刷新令牌实施轮换策略(每次使用后发放新的刷新令牌,并使旧的失效)。
2.7 完整序列图
下图展示了授权码模式的完整交互流程,包含所有五个阶段以及令牌刷新流程:
2.8 关键安全校验点总结
| 阶段 | 校验点 | 风险 |
|---|---|---|
| 授权码请求 | 验证 redirect_uri 白名单 | 开放重定向漏洞 |
| 用户授权 | 验证 state 参数一致性 | CSRF 攻击 |
| 令牌交换 | 验证授权码是否已使用、是否过期 | 授权码重用攻击 |
| 令牌交换 | 验证 client_secret 或客户端凭证 | 客户端身份伪造 |
| 刷新令牌 | 验证刷新令牌是否被吊销 | 令牌泄露持续访问 |
核心原则:授权码模式的安全性建立在“授权码一次性使用”、“后端通道令牌交换”、“state 防 CSRF”和“客户端认证”四重保障之上。任何一层的缺失都会导致安全漏洞。