OAuth 高级主题 #
概述 #
本文档探讨 OAuth 2.0 的高级特性和扩展,帮助你深入理解授权协议的高级应用。
text
┌─────────────────────────────────────────────────────────────┐
│ OAuth 高级主题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 令牌撤销 │ │ 令牌自省 │ │ 设备授权 │ │
│ │ Revocation │ │ Introspection│ │ Device Flow │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 令牌绑定 │ │ 资源指示器 │ │ JWT 断言 │ │
│ │ DPoP │ │Resource Indicators│ JWT Assertion│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
令牌撤销(Token Revocation) #
概述 #
令牌撤销(RFC 7009)允许客户端通知授权服务器令牌已不再需要,授权服务器可以使该令牌失效。
text
┌─────────────────────────────────────────────────────────────┐
│ 令牌撤销 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 使用场景: │
│ - 用户主动登出 │
│ - 应用卸载 │
│ - 令牌泄露 │
│ - 安全事件响应 │
│ │
│ 撤销端点: │
│ POST /revoke │
│ │
│ 可撤销的令牌: │
│ - 访问令牌 │
│ - 刷新令牌 │
│ │
└─────────────────────────────────────────────────────────────┘
撤销请求 #
http
POST /revoke HTTP/1.1
Host: auth.example.com
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
Content-Type: application/x-www-form-urlencoded
token=45ghiukldjahdnhzdauz
&token_type_hint=refresh_token
参数说明:
| 参数 | 必需 | 描述 |
|---|---|---|
| token | 是 | 要撤销的令牌 |
| token_type_hint | 否 | 令牌类型提示(access_token 或 refresh_token) |
撤销响应 #
成功响应:
http
HTTP/1.1 200 OK
错误响应:
json
{
"error": "unsupported_token_type",
"error_description": "The authorization server does not support the revocation of the presented token type."
}
实现示例 #
javascript
async function revokeToken(token, tokenType = 'refresh_token') {
const credentials = Buffer.from(
`${clientId}:${clientSecret}`
).toString('base64');
const response = await fetch('https://auth.example.com/revoke', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
token: token,
token_type_hint: tokenType
})
});
return response.ok;
}
await revokeToken(refreshToken, 'refresh_token');
令牌自省(Token Introspection) #
概述 #
令牌自省(RFC 7662)允许资源服务器查询令牌的状态和元数据。
text
┌─────────────────────────────────────────────────────────────┐
│ 令牌自省 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 使用场景: │
│ - 验证令牌有效性 │
│ - 获取令牌元数据 │
│ - 不透明令牌验证 │
│ │
│ 自省端点: │
│ POST /introspect │
│ │
│ 优势: │
│ - 实时验证令牌状态 │
│ - 支持令牌撤销检查 │
│ - 获取令牌详细信息 │
│ │
└─────────────────────────────────────────────────────────────┘
自省请求 #
http
POST /introspect HTTP/1.1
Host: auth.example.com
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
Content-Type: application/x-www-form-urlencoded
token=2YotnFZFEjr1zCsicMWpAA
&token_type_hint=access_token
自省响应 #
活跃令牌:
json
{
"active": true,
"scope": "profile email",
"client_id": "l238j323ds-23ij4",
"username": "jdoe",
"token_type": "Bearer",
"exp": 1419356238,
"iat": 1419350238,
"nbf": 1419350238,
"sub": "Z5O3upPC88QrAjx00dis",
"aud": "https://protected.example.net/resource",
"iss": "https://server.example.com/",
"jti": "abc123"
}
非活跃令牌:
json
{
"active": false
}
实现示例 #
javascript
async function introspectToken(token) {
const credentials = Buffer.from(
`${clientId}:${clientSecret}`
).toString('base64');
const response = await fetch('https://auth.example.com/introspect', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
token: token
})
});
const result = await response.json();
if (!result.active) {
throw new Error('Token is not active');
}
return result;
}
设备授权流程(Device Authorization Grant) #
概述 #
设备授权流程(RFC 8628)用于没有浏览器或输入受限的设备。
text
┌─────────────────────────────────────────────────────────────┐
│ 设备授权流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 适用场景: │
│ - 智能电视 │
│ - 智能音箱 │
│ - 打印机 │
│ - CLI 工具 │
│ - IoT 设备 │
│ │
│ 特点: │
│ - 设备显示验证码 │
│ - 用户在另一设备上验证 │
│ - 设备轮询获取令牌 │
│ │
└─────────────────────────────────────────────────────────────┘
流程图 #
text
┌─────────────────────────────────────────────────────────────┐
│ 设备授权流程图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 设备 │ │ 授权服务│ │ 用户 │ │
│ │ (CLI) │ │ │ │ (浏览器)│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ │ 1. 请求设备码 │ │ │
│ │──────────────>│ │ │
│ │ │ │ │
│ │ 2. 返回设备码 │ │ │
│ │ + 用户码 │ │ │
│ │<──────────────│ │ │
│ │ │ │ │
│ │ 3. 显示用户码 │ │ │
│ │ 和验证 URL │ │ │
│ │ │ │ │
│ │ │ 4. 访问验证 URL │
│ │ │<──────────────│ │
│ │ │ │ │
│ │ │ 5. 输入用户码 │ │
│ │ │<──────────────│ │
│ │ │ │ │
│ │ │ 6. 用户授权 │ │
│ │ │<──────────────│ │
│ │ │ │ │
│ │ 7. 轮询令牌 │ │ │
│ │──────────────>│ │ │
│ │ │ │ │
│ │ 8. 返回令牌 │ │ │
│ │<──────────────│ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────┘
设备授权请求 #
http
POST /device_authorization HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
client_id=your-client-id
&scope=profile%20email
设备授权响应 #
json
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNroNngQ3k0g8ZYhJTNK0TC2SCU62sFbPLtAcl",
"user_code": "WDJB-MJHT",
"verification_uri": "https://auth.example.com/device",
"verification_uri_complete": "https://auth.example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}
令牌轮询 #
http
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNroNngQ3k0g8ZYhJTNK0TC2SCU62sFbPLtAcl
&client_id=your-client-id
实现示例 #
javascript
async function deviceAuthorization() {
const deviceResponse = await fetch('https://auth.example.com/device_authorization', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: clientId,
scope: 'profile email'
})
});
const deviceData = await deviceResponse.json();
console.log(`请访问: ${deviceData.verification_uri}`);
console.log(`输入代码: ${deviceData.user_code}`);
const interval = deviceData.interval * 1000;
const expiresAt = Date.now() + deviceData.expires_in * 1000;
while (Date.now() < expiresAt) {
await new Promise(resolve => setTimeout(resolve, interval));
try {
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: deviceData.device_code,
client_id: clientId
})
});
const tokenData = await tokenResponse.json();
if (tokenData.error === 'authorization_pending') {
continue;
}
if (tokenData.error === 'slow_down') {
await new Promise(resolve => setTimeout(resolve, interval));
continue;
}
if (tokenData.access_token) {
return tokenData;
}
} catch (error) {
console.error('Token polling error:', error);
}
}
throw new Error('Device authorization expired');
}
DPoP(Demonstrating Proof-of-Possession) #
概述 #
DPoP(RFC 9449)是一种防止令牌被盗用的机制,通过绑定令牌到特定的客户端实例。
text
┌─────────────────────────────────────────────────────────────┐
│ DPoP 概述 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题: │
│ - Bearer 令牌可被任何持有者使用 │
│ - 令牌泄露后可被攻击者使用 │
│ │
│ 解决方案: │
│ - 客户端生成非对称密钥对 │
│ - 每个请求使用私钥签名 │
│ - 令牌绑定到公钥 │
│ - 只有拥有私钥的客户端能使用令牌 │
│ │
│ 优势: │
│ ✅ 防止令牌被盗用 │
│ ✅ 无需 TLS 客户端证书 │
│ ✅ 适用于 Bearer 令牌 │
│ │
└─────────────────────────────────────────────────────────────┘
DPoP 证明头 #
http
POST /resource HTTP/1.1
Host: resource.example.com
Authorization: DPoP <access_token>
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6Ik
VDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiYWNtSVZCWkg3RzJjNmdlZjR0UUxVbDl1Y2RM
UTdWdFZBaVhZZXZpWUZjbyIsInkiOiJBTy1ITnVvMkF1WkZfY3hQVjZ6QlFJUjFmN
kVLMHdDZGpTT0NpMjVjZGtjIn19.eyJqdGkiOiJkZjgxMzlmYS0wMTQzLTRhYTQtOT
M1YS1hNDEwMjA1YjY4YzUiLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9yZXNv
dXJjZS5leGFtcGxlLmNvbS9yZXNvdXJjZSIsImlhdCI6MTYxNDg4MTYwMH0.MEQCI
C7WjK9...
DPoP 证明 JWT 结构 #
json
{
"header": {
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "acmIVBZH7G2c6gef4tQLUl9ucdLQ7VtVAiXYeviYFco",
"y": "AO-HNuo2AuZF_cxPV6zBQIR1f6EK0wCdjSOCi25cdkcd"
}
},
"payload": {
"jti": "df8139fa-0143-4aa4-935a-a410205b68c5",
"htm": "POST",
"htu": "https://resource.example.com/resource",
"iat": 1614881600
}
}
实现示例 #
javascript
async function generateDPoPProof(method, url, keyPair) {
const header = {
typ: 'dpop+jwt',
alg: 'ES256',
jwk: {
kty: 'EC',
crv: 'P-256',
x: keyPair.publicKey.x,
y: keyPair.publicKey.y
}
};
const payload = {
jti: crypto.randomUUID(),
htm: method,
htu: url,
iat: Math.floor(Date.now() / 1000)
};
return await jwt.sign(payload, keyPair.privateKey, { header });
}
async function fetchWithDPoP(url, accessToken, keyPair) {
const dpopProof = await generateDPoPProof('GET', url, keyPair);
const response = await fetch(url, {
headers: {
'Authorization': `DPoP ${accessToken}`,
'DPoP': dpopProof
}
});
return response;
}
资源指示器(Resource Indicators) #
概述 #
资源指示器(RFC 8707)允许客户端在授权请求中指定要访问的资源。
text
┌─────────────────────────────────────────────────────────────┐
│ 资源指示器 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题: │
│ - 令牌可能被用于非预期的资源服务器 │
│ - 无法限制令牌的使用范围 │
│ │
│ 解决方案: │
│ - 授权请求中指定资源 URL │
│ - 令牌绑定到指定资源 │
│ - 资源服务器验证令牌受众 │
│ │
│ 参数: │
│ resource=https://api.example.com │
│ │
└─────────────────────────────────────────────────────────────┘
授权请求 #
http
GET /authorize?
response_type=code
&client_id=your-client-id
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback
&scope=read
&resource=https%3A%2F%2Fapi.example.com
&resource=https%3A%2F%2Fapi2.example.com HTTP/1.1
Host: auth.example.com
令牌响应 #
json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read"
}
JWT 断言授权 #
概述 #
JWT 断言授权允许使用 JWT 作为授权凭证获取令牌。
text
┌─────────────────────────────────────────────────────────────┐
│ JWT 断言授权 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 类型: │
│ 1. JWT Bearer Assertion (RFC 7523) │
│ - 使用 JWT 作为授权凭证 │
│ │
│ 2. SAML Bearer Assertion (RFC 7522) │
│ - 使用 SAML 断言作为授权凭证 │
│ │
│ 使用场景: │
│ - 联合身份认证 │
│ - 服务间授权委托 │
│ - 令牌交换 │
│ │
└─────────────────────────────────────────────────────────────┘
JWT 断言请求 #
http
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&scope=profile%20email
JWT 断言结构 #
json
{
"iss": "https://issuer.example.com",
"sub": "user-123",
"aud": "https://auth.example.com/token",
"exp": 1516239022,
"iat": 1516239022,
"jti": "unique-id",
"scope": "profile email"
}
令牌交换 #
概述 #
令牌交换(RFC 8693)允许客户端用一个令牌交换另一个令牌。
text
┌─────────────────────────────────────────────────────────────┐
│ 令牌交换 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 使用场景: │
│ - 委托授权 │
│ - 降级权限 │
│ - 跨域访问 │
│ - 模拟用户 │
│ │
│ 流程: │
│ 客户端 ──访问令牌──> 授权服务器 ──新令牌──> 客户端 │
│ │
└─────────────────────────────────────────────────────────────┘
令牌交换请求 #
http
POST /token HTTP/1.1
Host: auth.example.com
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https%3A%2F%2Fapi.example.com
&scope=read
令牌交换响应 #
json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read"
}
OAuth 2.1 概览 #
主要变化 #
text
┌─────────────────────────────────────────────────────────────┐
│ OAuth 2.1 变化 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 移除的功能: │
│ ❌ 简化模式(Implicit Grant) │
│ ❌ 密码模式(Resource Owner Password Credentials) │
│ │
│ 强制要求: │
│ ✅ 所有授权码请求必须使用 PKCE │
│ ✅ 必须使用 S256 code_challenge_method │
│ ✅ 必须使用 state 参数 │
│ ✅ refresh_token 必须单次使用或绑定发送者 │
│ │
│ 安全增强: │
│ ✅ 严格 redirect_uri 验证 │
│ ✅ 禁止 URL fragment 作为 redirect_uri │
│ ✅ 禁止通过 GET 请求获取令牌 │
│ │
└─────────────────────────────────────────────────────────────┘
总结 #
OAuth 2.0 是一个功能强大的授权框架,通过本文档介绍的高级特性,你可以:
text
┌─────────────────────────────────────────────────────────────┐
│ OAuth 高级特性总结 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 令牌管理: │
│ - 令牌撤销:主动使令牌失效 │
│ - 令牌自省:验证令牌状态 │
│ - 令牌交换:委托授权和权限降级 │
│ │
│ 安全增强: │
│ - PKCE:防止授权码劫持 │
│ - DPoP:证明令牌所有权 │
│ - 资源指示器:限制令牌使用范围 │
│ │
│ 特殊场景: │
│ - 设备授权:无浏览器设备授权 │
│ - JWT 断言:联合身份认证 │
│ │
│ 未来方向: │
│ - OAuth 2.1:更安全的默认配置 │
│ - 持续演进:新的扩展和最佳实践 │
│ │
└─────────────────────────────────────────────────────────────┘
参考资料 #
- RFC 6749: The OAuth 2.0 Authorization Framework
- RFC 7009: OAuth 2.0 Token Revocation
- RFC 7662: OAuth 2.0 Token Introspection
- RFC 8628: OAuth 2.0 Device Authorization Grant
- RFC 8693: OAuth 2.0 Token Exchange
- RFC 9449: OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP)
- RFC 8707: Resource Indicators for OAuth 2.0
- RFC 7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
最后更新:2026-03-28