3. 授权码流程完整时序:从用户点击登录到获取 Access Token 的完整 HTTP 请求/响应链
OAuth 2.0 授权码流程(Authorization Code Flow)是 Web 应用中最安全、最常用的授权模式。该流程涉及 资源所有者(用户)、用户代理(浏览器)、客户端(Web 应用)、授权服务器 和 资源服务器 五个参与方。理解完整的 HTTP 请求/响应链,是进行授权测试和边界验证的基础。
核心安全原则:授权码流程的关键在于
authorization_code 通过前端通道传递,而 access_token 仅通过后端安全通道(TLS)传输,确保令牌不被浏览器或第三方脚本截获。
3.1 完整时序步骤分解
从用户点击“使用第三方登录”到最终获取 access_token,共经历 6 个核心步骤,形成两条独立的通信通道:前端通道(浏览器 ↔ 授权服务器)和 后端通道(客户端 ↔ 授权服务器)。
| 步骤 | 发起方 | 接收方 | 通道类型 | 核心操作 |
|---|---|---|---|---|
| 1 | 用户(浏览器) | 客户端(Web 应用) | 前端 | 点击“登录”按钮,触发授权请求 |
| 2 | 客户端(302 重定向) | 授权服务器 | 前端 | 构造授权请求 URL,重定向用户浏览器 |
| 3 | 用户(浏览器) | 授权服务器 | 前端 | 用户认证并同意授权(输入凭据、确认 scope) |
| 4 | 授权服务器(302 重定向) | 客户端(回调端点) | 前端 | 返回 authorization_code 到回调 URL |
| 5 | 客户端(POST 请求) | 授权服务器(令牌端点) | 后端 | 用 authorization_code 换取 access_token |
| 6 | 授权服务器 | 客户端 | 后端 | 返回 access_token + refresh_token(可选) |
测试要点:步骤 2 和步骤 4 使用 HTTP 302 重定向,属于前端通道;步骤 5 和步骤 6 使用直接 POST/JSON 响应,属于后端通道。测试时应分别验证两条通道的完整性、参数校验和错误处理。
3.2 授权码流程完整时序图
下图展示了从用户点击登录到获取 Access Token 的完整 HTTP 请求/响应链,包含前端通道和后端通道的交互细节:
3.3 关键 HTTP 请求/响应详解
3.3.1 授权请求(步骤 2)
客户端构造授权请求 URL,通过 HTTP 302 重定向将用户浏览器导向授权服务器的 /authorize 端点:
GET /authorize?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://client.example.com/callback
&scope=openid%20profile%20email
&state=xyzABC123
&nonce=abcDEF456
HTTP/1.1
Host: auth-server.example.com
| 参数 | 必需 | 说明 | 测试关注点 |
|---|---|---|---|
response_type |
是 | 固定值 code,标识授权码流程 |
篡改为 token 是否被拒绝 |
client_id |
是 | 客户端标识符,由授权服务器预注册 | 使用未注册的 client_id 是否返回错误 |
redirect_uri |
是 | 授权码回调地址,必须与预注册一致 | 开放重定向漏洞、host 篡改 |
scope |
是 | 请求的权限范围,空格分隔 | 越权 scope 是否被限制 |
state |
强烈推荐 | CSRF 令牌,防止跨站请求伪造 | 缺失 state 是否被拒绝;state 可预测性 |
nonce |
OpenID 场景 | 防止重放攻击的一次性随机值 | nonce 重用是否被检测 |
3.3.2 授权码回调(步骤 4)
用户认证并授权后,授权服务器通过 302 重定向将 authorization_code 和 state 返回给客户端的回调端点:
HTTP/1.1 302 Found
Location: https://client.example.com/callback?code=AUTH_CODE_12345&state=xyzABC123
安全测试重点:授权码
(1) 重复使用同一授权码是否被拒绝;
(2) 授权码是否绑定到特定的
(3)
code 是一次性的,有效期通常为 1-10 分钟。测试时应验证:(1) 重复使用同一授权码是否被拒绝;
(2) 授权码是否绑定到特定的
redirect_uri 和 client_id;(3)
state 参数是否与发起请求时一致,防止 CSRF 攻击。
3.3.3 令牌请求(步骤 5)
客户端通过后端通道(服务器到服务器)向授权服务器的 /token 端点发送 POST 请求,使用 authorization_code 换取 access_token:
POST /token HTTP/1.1
Host: auth-server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code
&code=AUTH_CODE_12345
&redirect_uri=https://client.example.com/callback
&client_id=YOUR_CLIENT_ID
| 参数 | 必需 | 说明 | 测试关注点 |
|---|---|---|---|
grant_type |
是 | 固定值 authorization_code |
篡改为其他 grant_type 是否被拒绝 |
code |
是 | 上一步获取的授权码 | 过期、伪造、已使用的 code 处理 |
redirect_uri |
是 | 必须与授权请求中的 redirect_uri 一致 | 不一致时是否返回错误 |
client_id |
是 | 客户端标识符 | client_id 与 code 是否绑定 |
client_secret |
是 | 客户端密钥,通过 Basic Auth 或 POST body 传递 | 密钥泄露、弱密钥、空密钥测试 |
3.3.4 令牌响应(步骤 6)
授权服务器验证通过后,返回 access_token 和可选的 refresh_token:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN_67890",
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
边界验证要点:
- 令牌格式:验证
access_token是否为 JWT 格式,检查签名算法(推荐 RS256 而非 HS256) - 过期时间:
expires_in是否在合理范围(通常 3600 秒),是否存在永不过期的令牌 - scope 一致性:返回的
scope是否与授权请求一致,是否存在 scope 提升漏洞 - refresh_token:是否支持轮换(Rotation),旧 refresh_token 被使用后是否失效
- 响应头:
Cache-Control: no-store和Pragma: no-cache是否强制禁止缓存
3.4 错误处理与边界场景
授权码流程中,授权服务器在遇到错误时必须返回标准的错误响应。测试时应验证以下错误场景:
| 错误码 | 触发条件 | HTTP 状态码 | 测试方法 |
|---|---|---|---|
invalid_request |
缺少必需参数或参数格式错误 | 400 | 删除 response_type 或 client_id |
unauthorized_client |
客户端未授权使用该 grant_type | 401 | 使用未注册的 client_id |
access_denied |
用户拒绝授权 | 302 + error 参数 | 模拟用户点击“拒绝” |
invalid_grant |
授权码无效、过期或已被使用 | 400 | 重复使用同一授权码 |
redirect_uri_mismatch |
回调 URL 与预注册不一致 | 400 | 篡改 redirect_uri 参数 |
invalid_scope |
请求的 scope 无效或超出授权范围 | 400 | 请求未注册的 scope 值 |
Go 测试实践:使用
net/http/httptest 模拟授权服务器,编写表格驱动测试覆盖所有错误场景。重点验证授权码的 一次性、绑定性(绑定到 client_id + redirect_uri)和 时效性。
3.5 安全测试清单
基于完整时序链,总结以下安全测试要点:
- CSRF 防护:验证
state参数是否存在、是否随机、是否与服务端 session 绑定 - 开放重定向:测试
redirect_uri是否允许任意域名、路径穿越、协议混淆(http://vshttps://) - 授权码泄露:验证授权码是否出现在日志、Referer 头、浏览器历史中
- 授权码重用:同一授权码多次请求令牌端点是否返回
invalid_grant - 令牌劫持:验证
access_token是否仅通过后端通道传输,前端通道不得暴露令牌 - scope 提升:在令牌请求阶段修改 scope 参数是否被拒绝
- client_secret 强度:测试空密钥、弱密钥、硬编码密钥场景
- 令牌存储:验证服务端是否以安全方式存储令牌(加密、不记录日志)