第三方 OAuth 接入授权流程
换令牌与刷新
令牌端点的三种授权类型、客户端认证方式、刷新轮换语义。
令牌端点:POST {issuer}/token,请求体为 application/x-www-form-urlencoded,响应带 Cache-Control: no-store。
客户端认证
| 方式 | 适用 | 写法 |
|---|---|---|
client_secret_basic | 机密客户端(推荐) | Authorization: Basic base64(client_id:client_secret) |
client_secret_post | 机密客户端 | 表单里带 client_id 与 client_secret |
none | 公开客户端(web_client / desktop / ios_macos / android) | 表单里只带 client_id |
授权端点从不接受 client_secret
同意页的授权请求只认 client_id。把密钥塞进浏览器地址或表单,服务端会按非法请求处理。
机密客户端应把「取授权码」和「换令牌」分别放在前端与后端。
授权类型一:authorization_code
| 参数 | 必填 | 说明 |
|---|---|---|
grant_type | ✅ | authorization_code |
code | ✅ | 回调拿到的授权码 |
redirect_uri | ✅ | 与授权请求完全一致(回环端口仍按登记规则处理) |
code_verifier | ✅ | PKCE 原始值,必须能推出授权请求里的 code_challenge |
client_id | ✅(none 方式) | 公开客户端必带 |
服务端的校验顺序:客户端认证 → 授权码存在且未使用 → redirect_uri 一致 → PKCE 通过 → scope 裁剪。
成功后同时返回访问令牌与刷新令牌(仅当 scope 含 offline_access 时才含刷新令牌),请求 openid 时还返回 id_token。
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"scope": "openid profile email offline_access",
"id_token": "eyJ..."
}expires_in 的数值由回调平台决定:web_server 为 3600 秒,公开客户端平台为 1800 秒。
授权类型二:refresh_token
curl -X POST 'https://id.example.com/v1/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d 'grant_type=refresh_token' \
-d "refresh_token=$REFRESH_TOKEN"规则:
scope可选,且只能收窄:必须是原授权 scope 的子集,出现原授权之外的值会被拒。- 每次刷新都轮换:返回新的访问令牌与新的刷新令牌,旧刷新令牌立即失效。请用新值覆盖存储,不要保留旧值做「兜底重试」。
- 刷新令牌是签名 JWT,客户端不要解析其内容,也不要假设其长度或格式稳定。
- 刷新不恢复
authorization_details:细粒度授权只在首次授权时成立,刷新后令牌不再携带。
刷新令牌也有寿命
机密客户端 30 天,公开客户端 14 天,且不会因刷新而延长到超过上限。到期后必须让用户重新授权。
授权类型三:client_credentials
只对 machine 客户端开放,用于服务端到服务端调用,不涉及用户。
curl -X POST 'https://id.example.com/v1/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d 'grant_type=client_credentials' \
-d 'scope=auth:sessions:read'- 签发的令牌没有
sub,用principal字段区分「代表用户」还是「代表应用」。 - 不能带
offline_access:没有用户就没有可刷新的会话,请求该 scope 会被判invalid_scope。 - 第三方应用不需要这个授权类型;需要时说明产品形态应登记为
machine。
令牌交换
第一方管理端可以用 RFC 8693 换取其他受众的令牌,第三方用不到,见 令牌交换。
错误响应格式
令牌端点、撤销端点、内省端点遵循 RFC 6749 §5.2 的错误格式,不是控制台里那种统一信封:
{
"error": "invalid_grant",
"error_description": "The authorization code has already been used"
}error | HTTP | 含义 |
|---|---|---|
invalid_request | 400 | 缺参数、参数重复、grant_type 不受支持 |
invalid_client | 401 | 客户端认证失败(密钥错、方式不匹配) |
invalid_grant | 400 | 授权码/刷新令牌无效、已用、过期,PKCE 不匹配,redirect_uri 不一致 |
unauthorized_client | 400 | 该客户端无权使用此授权类型 |
invalid_scope | 400 | 申请的 scope 非法,或刷新时试图扩大 |
server_error | 500 | 服务端异常 |
限流触发时返回 429,使用统一信封并带 Retry-After 头,详见 错误码。