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