3. 授权码模式完整流程:用户授权、获取授权码、换取访问令牌、携带令牌访问资源、令牌刷新
授权码模式(Authorization Code Grant)是 OAuth 2.0 中最安全、应用最广泛的授权流程,特别适用于有后端服务器的 Web 应用。其核心思想是:授权码作为临时凭证在浏览器端传递,而访问令牌则通过安全的后端通道交换,避免令牌暴露在用户代理(浏览器)中。
完整流程包含五个关键步骤,每一步都涉及特定的端点、参数和安全约束。下面先通过一张整体流程图建立全局认知,再逐一拆解每个环节。
3.1 步骤一:用户授权(User Authorization)
流程的起点是用户试图访问客户端应用上的受保护资源。客户端检测到用户未登录或缺少有效令牌时,将用户引导至授权服务器的授权端点。
关键动作:客户端构造授权请求 URL,通过 302 重定向将用户浏览器发送到授权服务器。
// 授权请求 URL 示例
GET /authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://your-app.com/callback
&scope=openid%20profile%20email
&state=xyzABC123
HTTP/1.1
Host: authorization-server.com
| 参数 | 必填 | 说明 |
|---|---|---|
| response_type | 是 | 固定值 code,表示使用授权码模式 |
| client_id | 是 | 客户端在授权服务器注册时获得的标识 |
| redirect_uri | 是 | 授权成功后回调地址,必须与注册时一致 |
| scope | 否 | 请求的权限范围,空格分隔 |
| state | 强烈推荐 | 防 CSRF 攻击的随机字符串,回调时原样返回 |
安全要点:
state 参数是防止跨站请求伪造(CSRF)的关键手段。客户端必须生成不可预测的随机值,并在回调时严格校验。缺少 state 校验是生产环境中最常见的安全漏洞之一。
3.2 步骤二:获取授权码(Authorization Code)
用户到达授权服务器后,需要完成身份认证(登录)并授权客户端请求的权限。授权服务器验证用户身份后,生成一个临时的授权码,通过重定向将授权码发送回客户端指定的 redirect_uri。
// 授权成功后的回调 URL 示例
HTTP/1.1 302 Found
Location: https://your-app.com/callback?
code=AUTHORIZATION_CODE_12345
&state=xyzABC123
授权码特性:授权码是短生命期的临时凭证(通常 1-10 分钟),且只能使用一次。即使被截获,攻击者也难以在短时间内完成令牌交换,且无法重复使用。
3.3 步骤三:换取访问令牌(Access Token)
客户端收到授权码后,通过后端通道(服务器到服务器)向授权服务器的令牌端点发起 POST 请求,换取访问令牌。这一步是授权码模式的安全核心——令牌交换不经过浏览器,避免了令牌泄露。
// 令牌交换请求
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code
&code=AUTHORIZATION_CODE_12345
&redirect_uri=https://your-app.com/callback
// 令牌响应示例
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN_67890",
"scope": "openid profile email"
}
| 请求参数 | 必填 | 说明 |
|---|---|---|
| grant_type | 是 | 固定值 authorization_code |
| code | 是 | 上一步获取的授权码 |
| redirect_uri | 是 | 必须与授权请求中的值一致 |
| client_id / client_secret | 是 | 客户端身份认证,可通过 Basic Auth 或 POST 参数传递 |
安全关键:客户端凭证(client_secret)绝不能暴露在浏览器端。令牌交换请求必须从后端服务器发起,这是授权码模式区别于隐式模式的核心安全优势。
3.4 步骤四:携带令牌访问资源(Access Resource)
客户端获得访问令牌后,在请求受保护资源时,将令牌放入 HTTP 请求的 Authorization 头中,以 Bearer 令牌的形式发送给资源服务器。
// 携带令牌访问资源
GET /api/user/profile HTTP/1.1
Host: resource-server.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
资源服务器验证令牌的有效性(包括签名、过期时间、作用域等),如果验证通过,返回请求的资源数据;否则返回 401 Unauthorized 错误。
令牌验证方式:资源服务器可以通过两种方式验证令牌:
1. 自省(Introspection):将令牌发送给授权服务器验证,适用于不透明令牌。
2. 本地验证:如果使用 JWT 格式的令牌,资源服务器可自行验证签名和声明,无需额外网络请求。
1. 自省(Introspection):将令牌发送给授权服务器验证,适用于不透明令牌。
2. 本地验证:如果使用 JWT 格式的令牌,资源服务器可自行验证签名和声明,无需额外网络请求。
3.5 步骤五:令牌刷新(Token Refresh)
访问令牌通常有较短的有效期(如 1 小时)。当令牌过期时,客户端可以使用刷新令牌(Refresh Token)向授权服务器请求新的访问令牌,而无需用户重新授权。
// 令牌刷新请求
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN_67890
&scope=openid%20profile
// 刷新响应示例
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token": "NEW_ACCESS_TOKEN_ABC...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "NEW_REFRESH_TOKEN_DEF...",
"scope": "openid profile"
}
刷新令牌安全策略:
- 存储安全:刷新令牌必须安全存储在服务端,绝不能暴露给浏览器或前端代码。
- 轮换策略:每次刷新时,授权服务器可以返回新的刷新令牌并使旧令牌失效,防止令牌泄露后的长期滥用。
- 撤销机制:授权服务器应提供刷新令牌的撤销端点,当用户主动注销或检测到异常时,客户端应主动撤销刷新令牌。
3.6 流程总结与关键对比
| 步骤 | 参与者 | 通信通道 | 关键凭证 | 安全风险 |
|---|---|---|---|---|
| 用户授权 | 用户 → 浏览器 → 授权服务器 | 前端(浏览器重定向) | 无(用户认证) | 钓鱼、CSRF(state 防护) |
| 获取授权码 | 授权服务器 → 浏览器 → 客户端 | 前端(302 重定向) | 授权码(短生命期) | 授权码截获(短生命期+一次性) |
| 换取访问令牌 | 客户端 → 授权服务器 | 后端(服务器到服务器) | 授权码 + client_secret | 凭证泄露(TLS 加密) |
| 访问资源 | 客户端 → 资源服务器 | 后端 | 访问令牌 | 令牌泄露(Bearer 令牌) |
| 令牌刷新 | 客户端 → 授权服务器 | 后端 | 刷新令牌 | 刷新令牌泄露(轮换+撤销) |
最佳实践总结:
- 始终使用
state参数并严格校验,防止 CSRF 攻击。 - 授权码仅能使用一次,且有效期不超过 10 分钟。
- 令牌交换必须通过后端服务器完成,client_secret 绝不暴露。
- 访问令牌使用 Bearer 方案,通过
Authorization头传递。 - 刷新令牌实施轮换策略,并支持主动撤销。
- 所有通信必须使用 TLS/HTTPS 加密。