概述

2026年3月,IETF 发布了《AI Agent Authentication and Authorization》草案。这份文档由来自多家公司的专家联合编写,旨在为 AI Agent 的认证和授权提供一个基于现有标准的综合框架。

核心观点不发明新协议,而是组合现有成熟标准(WIMSE、SPIFFE、OAuth 2.0)来解决 AI Agent 的身份和授权问题。


前置概念:理解 IETF Draft 的技术基石

在深入解读这份 IETF Draft 之前,我们需要先理解它所依赖的核心技术栈。这份草案的核心理念是"组合而非发明"——它将多个成熟的身份与授权标准整合在一起,构建出一个适用于 AI Agent 的认证授权框架。

如果你已经熟悉 OAuth 2.0、OIDC、SPIFFE、WIMSE 这些概念,可以跳过本章节直接进入 AI Agent 是工作负载

如果你对这些概念一知半解,或者想了解它们在 AI Agent 场景下的特定含义,请继续阅读。

为什么 AI Agent 需要一套新的身份体系?

传统的身份认证体系是为人设计的:

  • 用户输入用户名密码
  • 系统验证后发放会话凭证
  • 用户凭借凭证访问资源

但 AI Agent 不是人:

  • 它没有密码,无法输入
  • 它可能 7×24 小时运行,会话概念不同
  • 它可能同时代表多个用户执行任务
  • 它的行为由 LLM 动态决策,不可完全预测

这意味着传统的"用户身份"体系无法直接套用到 AI Agent 上。我们需要一套**工作负载身份(Workload Identity)**体系——这正是 SPIFFE 和 WIMSE 要解决的问题。

同时,AI Agent 的行为需要被授权约束——它可以做什么、不可以做什么、代表谁做——这正是 OAuth 2.0 及其扩展要解决的问题。

以下是这些概念的详细解释。


OAuth 2.0:授权的基石

OAuth 2.0 解决什么问题?

OAuth 2.0 是一个授权框架,不是认证协议。

它解决的核心问题是:如何让一个应用在不获取用户密码的情况下,访问用户在另一个服务中的资源。

举个生活中的例子:

你在手机上用一个第三方照片打印 App,想让这个 App 访问你 Google 相册里的照片来下单打印。

传统方式:你把 Google 账号密码告诉这个 App → App 登录你的 Google 账号 → 获取照片 → 打印

问题:这个 App 拿到了你的密码,它可以做任何事——删照片、改密码、发邮件…

OAuth 2.0 方式:App 重定向你到 Google 登录页 → 你在 Google 页面输入密码并授权 → Google 给 App 一个有限权限的访问令牌 → App 用令牌只能读取照片,不能做其他事

OAuth 2.0 的核心角色

flowchart LR
    RO[Resource Owner
资源所有者
(用户)] C[Client
客户端
(第三方应用)] AS[Authorization Server
授权服务器
(如 Google)] RS[Resource Server
资源服务器
(如 Google 相册 API)] RO -->|"1. 授权"| C C -->|"2. 请求令牌"| AS AS -->|"3. 发放 Access Token"| C C -->|"4. 携带 Token 访问"| RS RS -->|"5. 返回资源"| C style RO fill:#228B22,color:#fff style C fill:#1565C0,color:#fff style AS fill:#5E35B1,color:#fff style RS fill:#B22222,color:#fff
角色说明例子
Resource Owner资源所有者,能决定授权给谁你(Google 账号持有者)
Client想访问资源的第三方应用照片打印 App
Authorization Server发放令牌的服务器Google OAuth 服务器
Resource Server存储资源的服务器Google 相册 API

核心流程:Authorization Code Flow

最常见的 OAuth 2.0 流程是 Authorization Code Flow:

sequenceDiagram
    participant User as 用户浏览器
    participant App as 第三方应用
    participant Auth as 授权服务器
    participant API as 资源服务器

    User->>App: 1. 点击"用 Google 登录"
    App->>User: 2. 重定向到授权服务器
(携带 client_id, redirect_uri, scope) User->>Auth: 3. 在授权服务器登录并授权 Auth->>User: 4. 重定向回 redirect_uri
(携带 authorization_code) User->>App: 5. App 收到 code App->>Auth: 6. 用 code + client_secret 换取 token Auth->>App: 7. 返回 access_token(可能还有 refresh_token) App->>API: 8. 携带 access_token 访问资源 API->>App: 9. 返回用户数据

关键点

  1. Authorization Code 是一次性的、短期的中间凭证
  2. Access Token 是最终的授权凭证,包含权限范围(scope)
  3. 用户密码只在授权服务器上输入,从不暴露给第三方应用

为什么需要 Authorization Code?

你可能会问:为什么不在用户登录授权后,直接返回 access_token,还要再用 authorization_code 换一次?

这是 OAuth 2.0 的核心安全设计。

直接返回 Token 的风险(Implicit Flow)

如果直接在浏览器重定向 URL 中返回 Token:

1
2
❌ 不安全的方式(Implicit Flow,已废弃):
https://your-app.com/callback#access_token=xxx

问题

风险说明
浏览器历史记录Token 会被记录在浏览器历史中,任何人查看历史都能看到
Referer 泄露如果页面加载了第三方资源(图片、脚本),Token 可能通过 Referer 请求头泄露给第三方
恶意脚本浏览器扩展、注入的恶意脚本可能读取 URL 中的 Token
服务器日志代理服务器、负载均衡器、CDN 可能记录完整 URL(包含 Token)
转发泄露用户复制 URL 分享给别人,Token 一起泄露
Authorization Code 的安全作用
1
2
3
✅ 安全的方式(Authorization Code Flow):
1. 浏览器重定向:https://your-app.com/callback?code=abc123
2. 后端用 code + client_secret 换取 Token

Authorization Code 的特性

特性说明
一次性的用完立即失效,无法重放攻击
短期的通常几分钟内过期
必须配对密钥只能在后端(有 client_secret)才能换取真正的 Token
绑定客户端只能由发起请求的 client_id 使用
无敏感信息code 本身不包含任何用户信息或权限信息

即使 code 泄露,攻击者没有 client_secret 也无法换取 Token。

flowchart TB
    subgraph 泄露场景["Code 泄露但 Token 安全"]
        A[攻击者截获 code] --> B[尝试换取 Token]
        B --> C{请求授权服务器}
        C -->|缺少 client_secret| D["❌ 被拒绝
攻击失败"] end subgraph 正常流程["正常授权流程"] E[用户获得 code] --> F[发送给应用后端] F --> G["后端用 code + client_secret
换取 Token"] G --> H["✅ 成功获取 Token"] end style D fill:#B22222,color:#fff style H fill:#228B22,color:#fff
两种流程的对比
flowchart TB
    subgraph 不安全["❌ Implicit Flow(OAuth 2.1 已废弃)"]
        A1[用户登录授权] --> A2[授权服务器]
        A2 -->|"直接返回 Token
在 URL fragment 中"| A3[浏览器] A3 -->|"Token 暴露在 URL 中"| A4["安全风险:
历史记录/Referer/脚本"] end subgraph 安全["✅ Authorization Code Flow"] B1[用户登录授权] --> B2[授权服务器] B2 -->|"返回 code
(无敏感信息)"| B3[浏览器] B3 -->|"code 传给后端"| B4[应用后端] B4 -->|"code + client_secret
(后端安全通信)"| B5[授权服务器] B5 -->|"返回 Token"| B4 end style A4 fill:#B22222,color:#fff style B4 fill:#228B22,color:#fff style B5 fill:#228B22,color:#fff
一句话总结

Authorization Code 是一个"一次性提货券",真正的 Token 只能在后端用"提货券 + 密钥"领取。即使"提货券"泄露,没有密钥也领不到 Token。

这就是为什么 OAuth 2.0 要"多此一举"——它把 Token 完全隔离在后端通信中,从不暴露给浏览器。

问题:既然 Token 不能出现在 URL 中,那存到 Cookie 是否就安全了?

答案:Cookie 比 URL 安全,但仍需正确配置。

Cookie vs URL 历史记录的本质区别

flowchart TB
    subgraph Cookie存储["Cookie 存储"]
        C1["✅ 不出现在 URL 中"]
        C2["✅ 不会通过 Referer 泄露"]
        C3["✅ 不会被代理/CDN 日志记录"]
        C4["✅ 跨域隔离(仅发给设置 Cookie 的域)"]
    end

    subgraph URL存储["URL 存储(Implicit Flow)"]
        U1["❌ 浏览器历史记录"]
        U2["❌ Referer 请求头泄露"]
        U3["❌ 代理/CDN 日志"]
        U4["❌ 用户复制分享"]
    end

    style C1 fill:#228B22,color:#fff
    style C2 fill:#228B22,color:#fff
    style C3 fill:#228B22,color:#fff
    style C4 fill:#228B22,color:#fff
    style U1 fill:#B22222,color:#fff
    style U2 fill:#B22222,color:#fff
    style U3 fill:#B22222,color:#fff
    style U4 fill:#B22222,color:#fff
维度CookieURL(历史记录)
自动发送✅ 仅对目标域❌ 可被复制到任何地方
Referer 泄露✅ 不会❌ 会泄露给第三方资源
日志记录✅ 不会被记录❌ 完整 URL 会被记录
用户可见⚠️ 需工具查看❌ 直接可见可复制

但 Cookie 有自己的攻击面

flowchart LR
    subgraph Cookie风险["Cookie 的安全风险"]
        A["XSS 攻击
恶意脚本读取 Cookie"] B["CSRF 攻击
伪造请求携带 Cookie"] C["中间人攻击
HTTP 传输被截获"] end subgraph 防护["防护措施"] D["HttpOnly
禁止 JS 访问"] E["SameSite=Strict
防 CSRF"] F["Secure
仅 HTTPS"] end A --> D B --> E C --> F style D fill:#228B22,color:#fff style E fill:#228B22,color:#fff style F fill:#228B22,color:#fff

Cookie 安全配置示例

1
2
3
4
5
6
Set-Cookie: id_token=eyJhbGci...;
  HttpOnly;           # 防 XSS
  Secure;             # 仅 HTTPS
  SameSite=Strict;    # 防 CSRF
  Path=/;
  Max-Age=3600        # 1小时过期
现代方案:PKCE

对于纯前端应用(没有后端,无法安全存储 client_secret),OAuth 2.1 推荐使用 PKCE(Proof Key for Code Exchange)

PKCE 是对 Authorization Code Flow 的增强,核心思想是:前端生成一个随机密钥,用它的哈希值作为"锁",换取 Token 时必须提供原密钥"开锁"。

PKCE 完整流程
sequenceDiagram
    participant FE as 前端应用
    participant Browser as 用户浏览器
    participant AS as 授权服务器

    Note over FE: 1. 生成随机 code_verifier
    FE->>FE: code_verifier = random(43-128字符)
    FE->>FE: code_challenge = SHA256(code_verifier)
    
    Note over FE,AS: 2. 授权请求(携带 code_challenge)
    FE->>Browser: 重定向到授权服务器
    Browser->>AS: GET /authorize?
client_id=xxx&
code_challenge=yyy&
code_challenge_method=S256 AS->>AS: 记录 code_challenge Note over Browser,AS: 3. 用户登录授权 Browser->>AS: 用户输入密码并授权 AS->>Browser: 重定向回前端
?code=abc123 Note over FE,AS: 4. 换取 Token(携带 code_verifier) Browser->>FE: 前端收到 code FE->>AS: POST /token
code=abc123&
code_verifier=原始密钥 AS->>AS: 验证 SHA256(code_verifier) == code_challenge AS->>FE: 返回 access_token

关键点

  • code_verifier:前端生成的随机密钥,从不通过浏览器 URL 传输
  • code_challengecode_verifier 的 SHA256 哈希值,在授权请求 URL 中公开传输
  • code:授权服务器返回的授权码,在重定向 URL 中公开传输
攻击场景分析

攻击者能截获什么?

数据传输方式攻击者能否截获说明
code_challenge授权请求 URL✅ 能浏览器历史记录、代理日志
code重定向 URL✅ 能浏览器历史记录、代理日志
code_verifierPOST 请求体❌ 不能不经过浏览器 URL,不记录在历史中
client_secret无(PKCE 不需要)-纯前端应用无法安全存储

攻击场景:攻击者截获 code

flowchart TB
    subgraph 攻击["攻击场景"]
        A1["攻击者截获 code"] --> A2["尝试换取 Token"]
        A2 --> A3["POST /token
code=abc123&
code_verifier=???"] A3 --> A4{"缺少 code_verifier"} A4 -->|无法通过验证| A5["❌ 被拒绝
攻击失败"] end style A5 fill:#B22222,color:#fff

攻击场景:攻击者同时截获 code 和 code_challenge

flowchart TB
    subgraph 攻击["攻击场景"]
        B1["攻击者截获 code + code_challenge"] --> B2["尝试换取 Token"]
        B2 --> B3["POST /token
code=abc123&
code_verifier=???"] B3 --> B4{"需要提供 code_verifier
使得 SHA256=code_challenge"} B4 -->|SHA256 单向,无法反推| B5["❌ 无法计算出 code_verifier
攻击失败"] end style B5 fill:#B22222,color:#fff

为什么攻击者无法伪造 code_verifier?

  • code_challenge = SHA256(code_verifier) 是单向哈希
  • 即使知道 code_challenge,也无法反推出 code_verifier
  • 攻击者必须提供正确的 code_verifier,否则授权服务器验证失败

总结

PKCE 的安全本质是:用单向哈希函数保护密钥。攻击者能截获 codecode_challenge,但没有 code_verifier,就无法通过授权服务器的验证。

场景推荐方式
有后端的应用Authorization Code Flow + client_secret
纯前端应用(SPA)Authorization Code Flow + PKCE
移动应用Authorization Code Flow + PKCE

Access Token 是什么?

Access Token 是一个字符串,代表持有者被授予的权限。

Access Token 的两种形态

问题:获取到的 Access Token 到底是什么?明文 JSON?加密密文?还是签名的 JWT?

答案:取决于授权服务器的实现,有两种形态:

形态格式示例特点
Opaque Token随机字符串a1b2c3d4e5f6g7h8i9j0不透明,需要查询授权服务器
JWT Token签名的 JWTeyJhbGciOiJSUzI1NiIs...透明,可本地验证和解析

Opaque Token(不透明令牌)

1
Access Token: 8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c
  • 就是一个随机字符串,没有结构
  • 资源服务器收到 Token 后,必须调用授权服务器的 introspection 端点验证
  • 授权服务器维护 Token 与权限的映射关系

JWT Token(结构化令牌)

1
Access Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJwaG90by1wcmludGVyIiwic2NvcGUiOiJyZWFkOnBob3RvcyIsImV4cCI6MTcxMDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • 是一个 JWT(JSON Web Token)
  • JWT 结构:header.payload.signature(三段用 . 分隔)
  • 不是加密的,是 Base64URL 编码 + 签名
JWT 的结构解析
flowchart LR
    subgraph JWT结构["JWT 三段结构"]
        direction LR
        H["Header
eyJhbGci..."] ~~~ P["Payload
eyJjbGll..."] ~~~ S["Signature
SflKxwR..."] end style H fill:#1565C0,color:#fff style P fill:#228B22,color:#fff style S fill:#B22222,color:#fff

解码后

Header(头部)

1
2
3
4
{
  "alg": "RS256",
  "typ": "JWT"
}
  • alg:签名算法(RS256 = RSA + SHA-256)
  • typ:类型(JWT)

Payload(负载)

1
2
3
4
5
6
7
{
  "client_id": "photo-printer-app",
  "scope": "read:photos",
  "aud": "https://photos.googleapis.com",
  "exp": 1710000000,
  "iat": 1709996400
}
  • client_id:谁拿到了这个 Token
  • scope:这个 Token 允许做什么
  • aud:这个 Token 能用于访问哪个服务(Audience)
  • exp:过期时间(Unix 时间戳)
  • iat:签发时间(Issued At)

Signature(签名)

1
2
3
4
RSASHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  privateKey
)
  • 用授权服务器的私钥签名
  • 资源服务器用公钥验证签名
关键问题:JWT 是加密的吗?

不是!JWT 是编码 + 签名,不是加密。

flowchart LR
    A["JWT Token
eyJhbGci..."] --> B[Base64URL 解码] B --> C["可以看到完整内容
(任何人都能看到)"] C --> D{验证签名} D -->|签名有效| E["✅ 内容未被篡改
可以信任"] D -->|签名无效| F["❌ 内容被篡改
拒绝使用"] style C fill:#E65100,color:#fff style E fill:#228B22,color:#fff style F fill:#B22222,color:#fff

这意味着

能做什么说明
✅ 能解码任何人都能用 Base64URL 解码看到内容
✅ 能验证用公钥验证签名,确认内容未被篡改
❌ 不能加密JWT 不加密,敏感信息不应放在 JWT 中
❌ 不能篡改签名保证内容不被修改(修改后签名失效)
获取方需要做什么处理?

场景 1:Opaque Token

sequenceDiagram
    participant Client as 客户端
    participant RS as 资源服务器
    participant AS as 授权服务器

    Client->>RS: 1. 请求资源
Authorization: Bearer a1b2c3d4... RS->>AS: 2. 调用 introspection 端点
POST /introspect AS->>AS: 3. 查询 Token 对应的权限 AS->>RS: 4. 返回 Token 信息 RS->>RS: 5. 检查权限 RS->>Client: 6. 返回资源

场景 2:JWT Token

sequenceDiagram
    participant Client as 客户端
    participant RS as 资源服务器
    participant AS as 授权服务器

    Note over RS: 首次启动时
    RS->>AS: 1. 获取公钥
GET /.well-known/jwks.json AS->>RS: 2. 返回 JWKS(公钥) Note over RS: 正常请求 Client->>RS: 3. 请求资源
Authorization: Bearer eyJhbGci... RS->>RS: 4. 本地验证签名
(用公钥) RS->>RS: 5. 检查 payload 中的权限 RS->>Client: 6. 返回资源

JWT Token 的优势:资源服务器不需要每次都查询授权服务器。

总结
问题答案
Access Token 是什么?一个字符串,代表授权
是明文 JSON 吗?可能是随机字符串(Opaque),也可能是 JWT
是加密的密文吗?不是,JWT 是编码 + 签名,不是加密
是签名的 JWT 吗?可能是,取决于授权服务器实现
获取方需要处理吗?Opaque:查询授权服务器;JWT:本地验证签名

OAuth 2.0 的局限性

OAuth 2.0 只解决授权问题,不解决身份认证问题

  • Access Token 告诉你"这个请求有权限读取照片"
  • 但它不告诉你"发起请求的用户是谁"

这就是为什么需要 OIDC。


OIDC:在授权之上加身份层

OAuth 2.0 缺了什么?

回到照片打印的例子:

  • 照片打印 App 拿到了 Access Token,可以读取你的照片
  • 但 App 想知道你的名字、邮箱,好显示"欢迎回来,张三"
  • OAuth 2.0 没有标准化这个"我是谁"的信息

早期的做法是每个平台自己定义接口(如 Google 的 /userinfo 端点),导致碎片化。

OIDC 的解决方案:id_token

OIDC(OpenID Connect)在 OAuth 2.0 之上增加了一个身份层,核心是引入 id_token

1
2
3
4
5
6
7
┌─────────────────────────────────────┐
│            OpenID Connect           │  ← 身份层:id_token("你是谁")
├─────────────────────────────────────┤
│             OAuth 2.0               │  ← 授权层:access_token("能做什么")
├─────────────────────────────────────┤
│              HTTP/TLS               │  ← 传输层
└─────────────────────────────────────┘

id_token 是一个 JWT,包含用户的身份声明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "header": {
    "alg": "RS256",
    "kid": "google-key-1"
  },
  "payload": {
    "iss": "https://accounts.google.com",
    "sub": "user-12345",
    "aud": "photo-printer-app",
    "name": "张三",
    "email": "zhangsan@example.com",
    "iat": 1709999900,
    "exp": 1710003500
  }
}
声明含义
iss签发者(Issuer),如 Google
sub主体(Subject),用户的唯一标识符
aud受众(Audience),这个 Token 发给谁
name用户姓名
email用户邮箱
iat签发时间
exp过期时间
id_token 的实际形态

问题:获取到的 id_token 到底是什么形态?需要做什么处理?

答案:id_token 必须是 JWT(OIDC 规范要求),结构如下:

flowchart LR
    subgraph JWT结构["id_token 三段结构"]
        direction LR
        H["Header
签名算法、Token类型"] ~~~ P["Payload
用户身份声明"] ~~~ S["Signature
授权服务器签名"] end style H fill:#1565C0,color:#fff style P fill:#228B22,color:#fff style S fill:#B22222,color:#fff

三段结构

内容编码方式
Header签名算法、Token 类型Base64URL 编码
Payload用户身份声明(sub, name, email…)Base64URL 编码
Signature用授权服务器私钥签名Base64URL 编码
客户端如何处理 id_token?
flowchart TB
    A["收到 id_token
eyJhbGci..."] --> B[1. 分割三段] B --> C[2. Base64URL 解码 Header 和 Payload] C --> D["3. 可以看到用户信息了
(但还不能信任)"] D --> E[4. 获取授权服务器公钥] E --> F[5. 验证签名] F --> G{签名有效?} G -->|是| H["✅ 可以信任用户身份
sub, name, email 等"] G -->|否| I["❌ Token 被篡改或伪造
拒绝登录"] style D fill:#E65100,color:#fff style H fill:#228B22,color:#fff style I fill:#B22222,color:#fff

详细步骤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import jwt
import requests

# 1. 获取 id_token(从授权服务器返回)
id_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

# 2. 获取授权服务器的公钥(通过 JWKS 端点)
jwks_url = "https://accounts.google.com/.well-known/jwks.json"
jwks = requests.get(jwks_url).json()

# 3. 解码并验证签名
decoded = jwt.decode(
    id_token,
    key=jwks,
    audience="photo-printer-app",  # 必须匹配你的 client_id
    algorithms=["RS256"]
)

# 4. 现在可以安全使用用户信息
print(f"用户 ID: {decoded['sub']}")
print(f"用户名: {decoded['name']}")
print(f"邮箱: {decoded['email']}")
关键验证点

客户端必须验证以下内容:

验证项说明失败后果
签名用公钥验证 Token 未被篡改Token 可能是伪造的
iss(签发者)必须是预期的授权服务器Token 来自不受信任的源
aud(受众)必须是自己的 client_idToken 发给别人,被你误用了
exp(过期时间)当前时间必须在过期之前Token 已失效
iat(签发时间)可选,检查 Token 不是太旧防止重放攻击

为什么必须验证?

flowchart LR
    A[攻击者] -->|"伪造 Token
sub=admin"| B[你的应用] B --> C{验证签名} C -->|不验证| D["❌ 信任了伪造的 Token
攻击者以 admin 身份登录"] C -->|验证| E["✅ 签名验证失败
拒绝登录"] style D fill:#B22222,color:#fff style E fill:#228B22,color:#fff
id_token vs Access Token
维度id_tokenAccess Token
用途身份认证(“你是谁”)授权访问资源(“能做什么”)
格式必须是 JWT可能是 Opaque 或 JWT
谁能解析客户端(你的应用)资源服务器(API)
包含什么用户身份信息(sub, name, email)权限范围(scope, aud)
如何使用验证后提取用户信息放在 HTTP Header 中访问 API
能否直接使用❌ 必须先验证签名✅ 直接发送给 API
一句话总结

id_token 是一个签名的 JWT,客户端解码后可以看到用户信息,但必须先用授权服务器的公钥验证签名,确保 Token 未被篡改。

scope 与 openid:理解授权范围

在深入 OIDC 流程之前,需要先理解 scopeopenid 这两个概念。它们是 OAuth 2.0/OIDC 中最基础但也最容易混淆的部分。

1. 为什么需要 scope?

问题场景

假设你的应用想访问用户在 Google 的资源:

  • 读取用户邮箱
  • 读取用户日历
  • 读取用户照片

你如何告诉 Google"我想要访问哪些资源"?

传统方式的问题

1
2
❌ 没有标准化方式:
"我想要访问用户的邮箱和日历"

每个授权服务器都有自己的接口和参数,应用需要针对每个服务适配。

OAuth 2.0 的解决方案scope 参数

1
2
✅ 标准化方式:
scope=email+calendar+photos.read

scope 的作用

作用说明
声明意图告诉授权服务器"我想要什么权限"
用户可见授权页面显示应用请求的权限(“此应用想要读取你的邮箱”)
权限隔离用户可以选择性授权(允许读取邮箱,但拒绝读取照片)
审计追踪Token 中记录授权范围,便于审计
2. scope 的实际格式

直截了当地说:scope 就是空格分隔的字符串。

1
2
3
4
5
6
7
8
9
# OAuth 2.0 授权请求 URL
GET /authorize?
  response_type=code&
  client_id=your-client-id&
  redirect_uri=https://your-app.com/callback&
  scope=openid+profile+email+photos.read

# 注意:+ 表示空格(URL 编码)
# 实际值是:"openid profile email photos.read"

关键点

问题答案
scope 是 JSON 吗?不是,就是简单的字符串
scope 是数组吗?不是,是空格分隔的字符串
如何在 URL 中表示?+%20 表示空格

每个 scope 的命名由授权服务器定义,没有统一标准:

授权服务器scope 示例
Googleopenid, profile, email, https://www.googleapis.com/auth/gmail.readonly
GitHubrepo, user, read:org
AWSaws.cognito.signin.user.admin
自定义你可以定义任何字符串,如 read:accounts:12345

命名约定

虽然命名没有标准,但常见模式有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 动作:资源 模式
read:photos
write:calendar
admin:users

# 资源.动作 模式
photos.read
calendar.write
users.admin

# URL 模式(Google 风格)
https://www.googleapis.com/auth/gmail.readonly

# 简单单词模式(GitHub 风格)
repo
user
read:org
3. openid:OIDC 的特殊标识符

问题:OAuth 2.0 只是授权框架,如何区分"普通 OAuth 请求"和"OIDC 身份认证请求"?

答案:用 openid 这个 scope 值作为标识符。

规范定义

openid 这个 scope 由 OpenID Connect Core 1.0 规范定义(Section 3.1.2.1):

The openid scope value MUST be present in the scope parameter.

直截了当地说:openid 就是一个字符串,字面值就是 "openid"

不是签名值,不是 hash 值,就是字面字符串 "openid"

为什么用 openid 这个名字?

这是 OIDC 从 OpenID 2.0 继承的命名约定。在 OpenID 2.0 时代,“openid” 就是身份认证的标识符,OIDC 为了兼容性和识别性沿用了这个命名。

4. 授权服务器如何处理 scope?
flowchart LR
    A[应用发起请求] --> B["构造 URL:
scope=openid+profile+email"] B --> C[发送到授权服务器] C --> D{服务器解析 scope 参数} D -->|发现 openid 字符串| E[按 OIDC 协议处理
返回 id_token + access_token] D -->|没有 openid 字符串| F[按 OAuth 2.0 处理
只返回 access_token] style E fill:#228B22,color:#fff style F fill:#1565C0,color:#fff

关键区别

场景scope 参数返回结果
OIDC 请求scope=openid+profile+emailid_token + access_token
OAuth 2.0 请求scope=photos.read+email只有 access_token

完整授权请求示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# OIDC 请求(Google)
https://accounts.google.com/o/oauth2/v2/auth?
  client_id=your-client-id&
  redirect_uri=https://your-app.com/callback&
  response_type=code&
  scope=openid+profile+email

# 关键:scope=openid+profile+email
# - openid:告诉 Google "这是 OIDC 请求"
# - profile:请求用户基本信息
# - email:请求用户邮箱
5. scope 的粒度限制

问题:scope 能表达多细的权限?

标准 OAuth 2.0 的限制

✅ 可以表达❌ 不能表达
photos.read 读取所有照片photos.read:12345 读取特定照片
accounts.read 读取所有账户accounts.read:12345 读取特定账户
calendar.read 读取所有日历calendar.read:work 读取工作日历

标准 scope 是粗粒度的:只能表达"能做什么类型的事",不能表达"对哪个具体资源做"。

6. 如何实现细粒度授权?

当需要"读取照片 12345"这种细粒度权限时,有几种方案:

方案说明适用场景
Resource 参数OAuth 2.0 Authorization Server Metadata,额外指定资源读取特定资源
Rich Authorization Requests (RAR)RFC 9396,用 JSON 描述复杂权限支付、多维度权限
Transaction TokenIETF Draft 中提到,绑定到单次交易转账、敏感操作
自定义 scope授权服务器支持动态 scope特定资源访问(需授权服务器支持)

方案 1:Resource 参数

1
2
3
4
5
GET /authorize?
  response_type=code&
  client_id=xxx&
  scope=read&
  resource=https://api.example.com/photos/12345

方案 2:Rich Authorization Requests (RAR)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
POST /authorize
Content-Type: application/json

{
  "type": "payment_initiation",
  "actions": ["create"],
  "locations": ["/payments"],
  "instruments": [
    {
      "type": "enduser_account_id",
      "value": "12345-67890"
    }
  ],
  "amount": {
    "currency": "USD",
    "value": "500.00"
  }
}

RAR 允许表达"向账户 12345 发起 500 美元的支付请求"这种复杂权限。

方案 3:Transaction Token

Token 中嵌入交易上下文:

1
2
3
4
5
6
7
8
{
  "scope": "write:transfers",
  "txn": {
    "account_id": "12345",
    "amount": "500.00",
    "recipient": "alice@example.com"
  }
}

这个 Token 只能用于这笔特定交易,修改任何参数都会导致验证失败。

7. 总结
问题答案
scope 是什么?空格分隔的字符串,声明应用请求的权限范围
scope 的格式?简单字符串(URL 中用 +%20 分隔)
scope 是 JSON 吗?不是,就是简单的字符串
openid 是什么?一个字符串,字面值是 "openid",OIDC 的标识符
为什么需要 openid?区分 OIDC 请求和普通 OAuth 2.0 请求
scope 能细粒度授权吗?标准 scope 是粗粒度的,但可通过 RAR/Resource/Transaction Token 实现

理解了 scope 和 openid 的本质后,我们再来看完整的 OIDC 使用流程。

OIDC 流程

OIDC 的流程与 OAuth 2.0 几乎一样,只是多返回一个 id_token

sequenceDiagram
    participant User as 用户
    participant App as 第三方应用
    participant Auth as OIDC Provider
(如 Google) User->>App: 1. 请求登录 App->>Auth: 2. 重定向到 OIDC Provider
(scope 包含 openid) User->>Auth: 3. 用户登录并授权 Auth->>App: 4. 返回 authorization_code App->>Auth: 5. 用 code 换取 token Auth->>App: 6. 返回 id_token + access_token Note over App: 从 id_token 解析出
用户身份信息

OIDC 的关键区别

理解了 Authorization Code 的安全设计后,OIDC 与普通 OAuth 2.0 的关键区别:

  • scope 中必须包含 openid(告诉授权服务器这是一个 OIDC 请求)
  • 返回的 token 中包含 id_token(而不仅仅是 access_token)
  • id_token 可以被应用直接解析验证(因为它是签名的 JWT)

简化版 OIDC:机器对机器的场景

标准 OIDC 流程面向有用户参与的浏览器场景。但在机器对机器(M2M)的场景中,没有"用户"输入密码,也不需要重定向和授权码。

oidc.cipherhub.cloud 与 AWS 的集成为例,这是一种直接签发模式

sequenceDiagram
    participant Client as 客户端(机器)
    participant OIDC as oidc.cipherhub.cloud
    participant AWS as AWS STS
    participant Resource as AWS 资源

    Note over Client: 1. 使用预置的私钥签名
    Client->>OIDC: 2. GET /generate-token
?audience=cipherhub-client
×tamp=...&nonce=...&signature=... OIDC->>OIDC: 3. 验证签名 + 时间戳 OIDC->>Client: 4. 返回 id_token Client->>AWS: 5. AssumeRoleWithWebIdentity
(id_token) AWS->>OIDC: 6. GET /.well-known/openid-configuration OIDC->>AWS: 7. 返回配置信息 AWS->>OIDC: 8. GET /keys OIDC->>AWS: 9. 返回 JWKS(公钥) AWS->>AWS: 10. 验证 id_token 签名 AWS->>Client: 11. 返回临时凭证
(AccessKey + SecretKey + SessionToken) Client->>Resource: 12. 使用临时凭证访问 AWS 资源 Resource->>Client: 13. 返回数据

流程说明

  1. 客户端认证:客户端使用预置的 RSA 私钥对请求参数(audience + timestamp + nonce)签名
  2. 获取 id_token:调用 /generate-token,服务端验证签名后直接发放 id_token
  3. 换取 AWS 凭证:客户端调用 AWS STS AssumeRoleWithWebIdentity,传入 id_token
  4. AWS 验证:AWS 请求 OIDC 服务器的 /.well-known/openid-configuration/keys 端点,获取公钥验证 id_token
  5. 获取临时凭证:验证通过后,AWS 返回临时凭证(有效期通常 1 小时)
  6. 访问资源:客户端使用临时凭证访问 AWS 服务(如 KMS、Secrets Manager)
id_token 的实际结构(oidc.cipherhub.cloud 实现示例)

以下是 oidc.cipherhub.cloud/generate-token 端点返回的 id_token 的实际结构:

JWT Header

1
2
3
4
5
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "cipherhub-key-v1"
}

JWT Payload

1
2
3
4
5
6
7
{
  "iss": "https://oidc.cipherhub.cloud",
  "aud": "cipherhub-client",
  "sub": "cipherhub-wayfarer",
  "iat": 1730123456,
  "exp": 1730127056
}

Payload 字段说明

字段说明示例值
iss发行者(Issuer),必须与 OIDC 配置中的 issuer 一致https://oidc.cipherhub.cloud
aud受众(Audience),标识 Token 的接收方cipherhub-client
sub主体(Subject),身份标识符cipherhub-wayfarer
iat签发时间(Issued At),Unix 时间戳1730123456
exp过期时间(Expiration),Unix 时间戳1730127056(1 小时后)

关键约束

  • iss 必须与 /.well-known/openid-configuration 返回的 issuer 字段完全一致
  • aud 必须在 audiences.yaml 配置的允许列表中
  • exp - iat 建议在 3600 秒(1 小时)以内
  • Token 用 RS256 算法签名,公钥通过 /keys 端点发布

完整 id_token 示例(Base64URL 编码后):

1
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImNpcGhlcmh1Yi1rZXktdjEifQ.eyJpc3MiOiJodHRwczovL29pZGMuY2lwaGVyaHViLmNsb3VkIiwiYXVkIjoiY2lwaGVyaHViLWNsaWVudCIsInN1YiI6ImNpcGhlcmh1Yi13YXlmYXJlciIsImlhdCI6MTczMDEyMzQ1NiwiZXhwIjoxNzMwMTI3MDU2fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c...

说明:以上结构基于 oidc.cipherhub.cloud 的实际实现,作为简化版 OIDC 的参考示例。完整实现代码可参考项目源码。

id_token 的实际结构(oidc.cipherhub.cloud 实现示例)

以下是 oidc.cipherhub.cloud/generate-token 端点返回的 id_token 的实际结构:

JWT Header

1
2
3
4
5
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "cipherhub-key-v1"
}

JWT Payload

1
2
3
4
5
6
7
{
  "iss": "https://oidc.cipherhub.cloud",
  "aud": "cipherhub-client",
  "sub": "cipherhub-wayfarer",
  "iat": 1730123456,
  "exp": 1730127056
}

Payload 字段说明

字段说明示例值
iss发行者(Issuer),必须与 OIDC 配置中的 issuer 一致https://oidc.cipherhub.cloud
aud受众(Audience),标识 Token 的接收方cipherhub-client
sub主体(Subject),身份标识符cipherhub-wayfarer
iat签发时间(Issued At),Unix 时间戳1730123456
exp过期时间(Expiration),Unix 时间戳1730127056(1 小时后)

关键约束

  • iss 必须与 /.well-known/openid-configuration 返回的 issuer 字段完全一致
  • aud 必须在 audiences.yaml 配置的允许列表中
  • exp - iat 建议在 3600 秒(1 小时)以内
  • Token 用 RS256 算法签名,公钥通过 /keys 端点发布

完整 id_token 示例(Base64URL 编码后):

1
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImNpcGhlcmh1Yi1rZXktdjEifQ.eyJpc3MiOiJodHRwczovL29pZGMuY2lwaGVyaHViLmNsb3VkIiwiYXVkIjoiY2lwaGVyaHViLWNsaWVudCIsInN1YiI6ImNpcGhlcmh1Yi13YXlmYXJlciIsImlhdCI6MTczMDEyMzQ1NiwiZXhwIjoxNzMwMTI3MDU2fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c...

说明:以上结构基于 oidc.cipherhub.cloud 的实际实现,作为简化版 OIDC 的参考示例。完整实现代码可参考项目源码。

标准版 vs 简化版对比

维度标准 Authorization Code Flow简化版(如 oidc.cipherhub.cloud)
目标场景有用户参与的浏览器应用机器对机器(M2M)
用户认证用户名密码 / MFA / 社交登录不适用(无用户)
客户端认证client_id + client_secret证书签名(RSA-SHA256)
重定向需要(授权码模式)不需要
授权码先拿 code,再换 token直接发放 id_token
防重放state 参数 + 授权码一次性使用时间戳(±3s)+ 随机 Nonce
Token 传输保护授权码→后端换 Token全程服务端通信
适用场景Web 应用、移动应用、社交登录服务器、CI/CD、自动化任务

简化模式的安全性

简化流程并不意味着降低安全性,只是安全机制的侧重不同:

  • 标准流程:主要防御浏览器环境威胁(CSRF、Token 泄露到 URL、开放重定向等)
  • 简化模式:引入证书签名鉴权,其安全强度实际上高于 client_id + client_secret,因为私钥永远不需要在网络上传输

什么时候不能用简化模式?

如果你的场景涉及:

  • 真实用户在浏览器中登录
  • 需要用户主动授权同意
  • 需要对接第三方社交登录(Google、GitHub 等)

那就必须使用标准 OIDC 流程。简化模式专门服务于机器对机器的信任传递。

参考:关于简化版 OIDC 的详细实现,可参考 oidc.cipherhub.cloud 与 AWS 联合身份配置

OIDC 与 AI Agent 的关系

当 AI Agent 需要代表用户访问资源时(比如帮用户查询银行账户),OIDC 提供了用户身份的标准化传递机制:

  • Agent 持有用户的 Access Token(代表授权)
  • Agent 可以从 id_token 获取用户身份(代表委托关系)

但这里有个关键问题:Agent 本身的身份如何管理?

OIDC 解决的是"用户是谁",而不是"Agent 是谁"。


SSO:单点登录的体验目标

SSO 是什么?

SSO(Single Sign-On,单点登录)不是一种协议,而是一种用户体验目标。

它的含义很简单:用户登录一次,就能访问多个系统,无需在每个系统中重复输入密码。

flowchart LR
    User[用户] -->|登录一次| SSO[SSO 系统]
    SSO -->|无需再次登录| App1[应用 A]
    SSO -->|无需再次登录| App2[应用 B]
    SSO -->|无需再次登录| App3[应用 C]
    
    style User fill:#228B22,color:#fff
    style SSO fill:#1565C0,color:#fff
    style App1 fill:#5E35B1,color:#fff
    style App2 fill:#5E35B1,color:#fff
    style App3 fill:#5E35B1,color:#fff

SSO 的实现方式

SSO 是目标,OIDC 和 SAML 是实现这个目标的技术手段:

1
2
3
4
5
6
7
8
                ┌─────────────────┐
                │   SSO(目标)    │
                └────────┬────────┘
            ┌────────────┼────────────┐
            ▼            ▼            ▼
         ┌──────┐   ┌────────┐   ┌───────────┐
         │ OIDC │   │ SAML   │   │ CAS / 其他 │
         └──────┘   └────────┘   └───────────┘
技术时代适用场景
SAML 2.02005 年企业 SSO(AD FS、Shibboleth),依赖浏览器和 XML
OIDC2014 年互联网/云原生(Google、GitHub),基于 JSON/JWT
CAS早期校园网 SSO,现在较少使用

OIDC 实现 SSO 的原理

当你用 Google 账号登录多个网站时:

  1. 第一次登录网站 A

    • 网站重定向到 Google 登录页
    • 你输入 Google 密码
    • Google 发放 id_token 给网站 A
    • 浏览器在 Google 域下记录登录状态(Cookie)
  2. 随后登录网站 B

    • 网站重定向到 Google 登录页
    • Google 检测到浏览器已有登录 Cookie
    • Google 直接发放 id_token 给网站 B,无需再次输入密码

这就是 OIDC 实现 SSO 的机制。

SSO 与 AI Agent 的关系

在 AI Agent 场景中,SSO 的重要性体现在:

  1. Agent 代理用户访问多个系统

    • 用户登录一次
    • Agent 可以代表用户访问邮件、日历、文件等多个系统
    • 无需在每个系统中单独授权
  2. 统一身份上下文

    • Agent 通过 SSO 获取用户的统一身份
    • 在不同系统间保持一致的委托关系
  3. 用户体验

    • 用户不需要反复确认授权
    • Agent 可以持续工作,不需要频繁中断用户

但同样,SSO 解决的是用户的单点登录,Agent 自身的身份管理仍然需要工作负载身份机制(如 SPIFFE)。


SPIFFE:给工作负载发身份

为什么需要工作负载身份?

在微服务和云原生环境中,有大量的**工作负载(Workload)**在运行:

  • 容器、Pod、进程
  • 服务 A 调用服务 B
  • 数据库、缓存、消息队列

这些工作负载之间也需要相互认证:

  • 服务 B 如何验证请求确实来自服务 A?
  • 如何防止攻击者伪造服务 A 的身份?

传统方案的问题:

方案问题
API Key静态、长期、难轮换、泄露影响大
密码需要安全存储、分发困难
IP 白名单动态环境中 IP 不固定
OIDC为用户设计,不适用于机器

SPIFFE(Secure Production Identity Framework for Everyone) 就是解决这个问题的。

SPIFFE 核心概念

1. SPIFFE ID:工作负载的身份证号

每个工作负载都有一个全局唯一的标识符:

1
spiffe://<trust-domain>/<path>

示例

1
2
3
spiffe://acme.com/ns/default/sa/frontend
spiffe://staging.example.com/service/database
spiffe://prod.example.com/department/backend/service/payment
  • trust-domain:信任域,定义信任边界(如 acme.com
  • path:工作负载的路径,由组织自行定义
2. Trust Domain:信任边界

Trust Domain 是一个管理边界,代表一组共享信任根的工作负载:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
┌────────────────────────────────────────┐
│     Trust Domain: production           │
│                                        │
│  spiffe://production/... ← 信任同一套根  │
│                                        │
│  ┌─────────┐  ┌─────────┐  ┌────────┐ │
│  │ Service │  │ Service │  │  DB    │ │
│  │    A    │  │    B    │  │        │ │
│  └─────────┘  └─────────┘  └────────┘ │
└────────────────────────────────────────┘

同一个 Trust Domain 内的工作负载互相认证;不同 Trust Domain 之间需要建立联邦

3. SVID:可验证的身份凭证

SVID(SPIFFE Verifiable Identity Document) 是绑定了 SPIFFE ID 的凭证,有两种形式:

类型用途特点
X.509-SVIDmTLS、服务间认证短期证书(1-24小时),自动轮换
JWT-SVIDHTTP 头、应用层更短(5-15分钟),方便传递

X.509-SVID 结构

1
2
3
4
5
6
7
Certificate:
    Subject: CN=spiffe://production/ns/default/sa/frontend
    Issuer: CN=Production Intermediate CA
    Validity: 1 hour
Extensions:
    Subject Alternative Name (SAN):
        URI: spiffe://production/ns/default/sa/frontend

SPIFFE ID 被放在证书的 SAN 扩展中。

JWT-SVID 结构

1
2
3
4
5
{
  "sub": "spiffe://production/ns/default/sa/frontend",
  "aud": ["spiffe://production/ns/api/sa/backend"],
  "exp": 1710000000
}
4. Workload API:零配置获取身份

工作负载如何获取 SVID?

SPIFFE 定义了 Workload API

sequenceDiagram
    participant Workload as 工作负载
    participant Agent as SPIFFE Agent
    participant Server as SPIFFE Server

    Workload->>Agent: 1. FetchX509SVID()
    Note over Agent: 验证工作负载身份
(通过内核/编排器) Agent->>Server: 2. 申请 SVID Server->>Agent: 3. 签发 X.509-SVID Agent->>Workload: 4. 返回 SVID + 私钥 + Trust Bundle Note over Workload: 使用 SVID 进行 mTLS

关键特性

  • 工作负载无需预置凭证即可调用 API
  • Agent 通过带外验证(如 Kubernetes ServiceAccount、Unix UID)识别调用者
  • SVID 自动轮换,工作负载无需关心

SPIFFE 与 OIDC 的对比

维度OIDCSPIFFE
目标对象用户工作负载(机器)
身份标识用户 ID(sub)SPIFFE ID
凭证类型id_token(JWT)X.509-SVID / JWT-SVID
获取方式用户登录授权Workload API 自动获取
生命周期与用户会话相关独立管理,自动轮换
典型场景社交登录、SSO服务网格、微服务认证

简单说:OIDC 给人发身份证,SPIFFE 给机器发身份证。


WIMSE:SPIFFE 的泛化扩展

WIMSE 是什么?

WIMSE(Workload Identity in Multi-System Environments) 是一个正在标准化的工作负载身份框架。

SPIFFE 是 WIMSE 的一种实现,但 WIMSE 更加泛化:

1
2
spiffe://trust-domain/path     ← SPIFFE ID(WIMSE 的一种格式)
wimse://trust-domain/path      ← WIMSE ID(更通用的格式)

WIMSE 标识符格式

1
wimse://<trust-domain>/<path>

与 SPIFFE ID 格式几乎相同,但:

  • WIMSE 是更上层的抽象
  • SPIFFE 是 WIMSE 的一种具体实现
  • WIMSE 可以支持其他格式的凭证和协议

IETF Draft 为什么选择 WIMSE?

这份 IETF Draft 使用 WIMSE 作为 Agent 标识符,原因:

  1. 标准化进程:WIMSE 正在 IETF 标准化,比 SPIFFE 更"官方"
  2. 更广泛的适用性:不限定于 SPIFFE 的实现方式
  3. 向后兼容:SPIFFE ID 可以直接作为 WIMSE ID 使用

Draft 中的规定

每个 Agent 必须有且只有一个 WIMSE 标识符。 该标识符可以是 SPIFFE ID。

这意味着你可以:

  • 直接使用现有的 SPIFFE 基础设施
  • 或者实现自己的 WIMSE 兼容系统

WIT:Workload Identity Token

WIMSE 还引入了 WIT(Workload Identity Token),类似于 SPIFFE 的 JWT-SVID:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "header": {
    "typ": "workload-identity-token",
    "alg": "ES256"
  },
  "payload": {
    "iss": "https://wimse.example.com",
    "sub": "wimse://production/agent/finance-analyzer",
    "aud": "https://api.example.com",
    "exp": 1710000000
  }
}

前置概念小结:如何组合成 AI Agent 认证框架?

现在我们有了所有的"积木":

flowchart TB
    subgraph 身份层["身份层:识别 Agent 是谁"]
        S1[SPIFFE/WIMSE 标识符]
        S2[X.509-SVID / WIT 凭证]
    end

    subgraph 认证层["认证层:证明 Agent 身份"]
        A1[mTLS 传输层认证]
        A2[WPT/HTTP Signature 应用层认证]
    end

    subgraph 授权层["授权层:决定 Agent 能做什么"]
        O1[OAuth 2.0 委托授权]
        O2[Access Token]
        O3[Transaction Token]
    end

    subgraph 用户身份["用户身份:Agent 代表谁"]
        U1[OIDC id_token]
    end

    身份层 --> 认证层
    认证层 --> 授权层
    U1 --> 授权层

    style S1 fill:#228B22,color:#fff
    style S2 fill:#228B22,color:#fff
    style A1 fill:#1565C0,color:#fff
    style A2 fill:#1565C0,color:#fff
    style O1 fill:#5E35B1,color:#fff
    style O2 fill:#5E35B1,color:#fff
    style O3 fill:#5E35B1,color:#fff
    style U1 fill:#B22222,color:#fff

组合逻辑

  1. Agent 自己的身份(SPIFFE/WIMSE)

    • 每个 Agent 有唯一的 WIMSE/SPIFFE ID
    • 持有 X.509-SVID 或 WIT 证明身份
    • 通过 mTLS 或 HTTP Signature 认证
  2. Agent 代表用户时的身份(OIDC + OAuth 2.0)

    • 用户的 id_token 证明"用户是谁"
    • OAuth Access Token 证明"用户授权 Agent 做什么"
  3. 细粒度授权(Transaction Token)

    • 单次交易内有效的短期 Token
    • 绑定到具体操作上下文

这就是 IETF Draft 的核心设计——用现有的、成熟的身份与授权标准,组合出一套适用于 AI Agent 的认证授权体系


理解了这些前置概念后,我们就可以深入解读 IETF Draft 的具体内容了。

AI Agent 是工作负载(Workload)

定义

An Agent is a workload that iteratively interacts with a Large Language Model (LLM) and a set of Tools, Services and Resources.

文档将 AI Agent 明确定义为工作负载,而非传统的客户端应用。这一区分至关重要:

特征传统客户端AI Agent(Workload)
身份管理用户身份工作负载身份
凭证类型长期密钥/API Key短期 X.509/JWT/WIT
授权模式用户授权委托授权 + 自主授权
生命周期与用户会话绑定独立生命周期管理

Agent 交互模型

flowchart TB
    subgraph 输入层
        U[用户/系统]
    end

    subgraph Agent层
        A[AI Agent
Workload] end subgraph 推理层 LLM[Large Language Model] end subgraph 资源层 T[Tools] S[Services] R[Resources] end U -->|"1. 初始请求"| A A -->|"2. 提供上下文"| LLM LLM -->|"3. 返回决策"| A A -->|"4. 调用端点"| T A -->|"4. 调用端点"| S A -->|"4. 调用端点"| R T -->|"5. 返回结果"| A S -->|"5. 返回结果"| A R -->|"5. 返回结果"| A A -->|"6. 最终响应"| U style A fill:#1565C0,color:#fff style LLM fill:#5E35B1,color:#fff style T fill:#228B22,color:#fff style S fill:#228B22,color:#fff style R fill:#228B22,color:#fff

交互流程

  1. 用户或系统向 Agent 提供初始请求
  2. Agent 将上下文(系统提示、工具描述、历史输出等)发送给 LLM
  3. LLM 返回决策,指导 Agent 选择调用哪些工具/服务
  4. Agent 调用选定的外部端点
  5. 外部资源返回结果,Agent 可能将其作为新上下文发送给 LLM
  6. 重复 2-5 直到满足退出条件,Agent 返回最终响应

Agent Identity Management System (AIMS)

文档定义了一个概念模型:Agent Identity Management System (AIMS),描述管理 Agent 身份和权限所需的全部功能。

AIMS 组件栈

flowchart LR
    subgraph L1["基础层"]
        A1["1. 标识符
Identifiers"] A2["2. 凭证
Credentials"] A3["3. 证明
Attestation"] end subgraph L2["运行层"] B1["4. 分发
Provisioning"] B2["5. 认证
Authentication"] B3["6. 授权
Authorization"] end subgraph L3["管理层"] C1["7. 监控
Observability"] C2["8. 策略
Policy"] C3["9. 合规
Compliance"] end A1 --> A2 --> A3 --> B1 --> B2 --> B3 --> C1 --> C2 --> C3 style A1 fill:#228B22,color:#fff style A2 fill:#228B22,color:#fff style A3 fill:#228B22,color:#fff style B1 fill:#1565C0,color:#fff style B2 fill:#1565C0,color:#fff style B3 fill:#1565C0,color:#fff style C1 fill:#B22222,color:#fff style C2 fill:#B22222,color:#fff style C3 fill:#B22222,color:#fff

各层职责

层级组件职责
1. 标识符Agent Identifiers分配唯一标识符
2. 凭证Agent Credentials加密绑定标识符与属性
3. 证明Agent Attestation基于环境测量发放凭证
4. 凭证分发Credential Provisioning运行时发放、轮换、生命周期管理
5. 认证Agent Authentication向 LLM/工具证明身份
6. 授权Agent Authorization决定是否允许访问资源
7. 监控Observability & Remediation基于行为动态调整授权
8. 策略Auth Policy配置和规则
9. 合规Agent Compliance审计与策略符合性

Agent 标识符(WIMSE/SPIFFE)

强制要求

Agents MUST be uniquely identified in order to support authentication, authorization, auditing, and delegation.

WIMSE 标识符

WIMSE(Workload Identity in Multi-System Environments) 是主要标识符格式:

1
wimse://<trust-domain>/<path>

SPIFFE 标识符

SPIFFE(Secure Production Identity Framework for Everyone) 是 WIMSE 的成熟实现:

1
spiffe://<trust-domain>/<path>

示例

1
2
spiffe://acme.com/agent/finance-analyzer
spiffe://trustcorp.io/agent/customer-support-bot

要求

  • 每个 Agent 必须有且只有一个 WIMSE 标识符
  • 该标识符可以是 SPIFFE ID
  • 标识符在身份生命周期内必须保持稳定

Agent 凭证

为什么不能用 API Key?

Static API keys are an antipattern for agent identity. They are bearer artifacts that are not cryptographically bound, do not convey identity, are typically long-lived and are operationally difficult to rotate.

问题API Key工作负载凭证
身份绑定❌ 无法证明持有者身份✅ 加密绑定到标识符
生命周期❌ 通常长期有效✅ 短期自动轮换
撤销机制❌ 手动撤销困难✅ 过期自动失效
审计追踪❌ 无法关联到具体工作负载✅ 可追溯到身份

支持的凭证类型

凭证类型来源特点
X.509-SVIDSPIFFEmTLS 认证
JWT-SVIDSPIFFE应用层认证
WIT (Workload Identity Token)WIMSE工作负载身份令牌

凭证要求

  1. 必须短期:包含明确的过期时间
  2. 加密绑定:凭证与标识符通过公钥绑定
  3. 可携带属性:信任域、证明证据、工作负载元数据等

二级凭证交换

问题场景:不同环境有不同的凭证格式

前文提到,Agent 持有的凭证(如 X.509-SVID、JWT-SVID、WIT)是由 SPIFFE 或 WIMSE 系统发放的。这些是 Agent 的主凭证——证明"我是谁"的初始身份凭证。

但现实世界是:不同的云厂商、不同的服务有自己的凭证体系

举个例子:

  • 你的 Agent 持有 X.509-SVID(来自 SPIFFE 系统)
  • Agent 需要访问 AWS KMS 加密数据
  • AWS 不认识 X.509-SVID,它只接受 AWS STS Token

这就是"不支持主凭证格式"的场景:目标环境无法直接验证你持有的凭证。

flowchart LR
    subgraph 你的系统["你的系统(SPIFFE/WIMSE)"]
        A[Agent]
        B[X.509-SVID / WIT
主凭证] end subgraph AWS环境["AWS 环境"] C[AWS KMS] D[AWS STS Token
AWS 认识的凭证] end A -->|持有| B B -.->|AWS 不认识| C D -->|AWS 接受| C style B fill:#228B22,color:#fff style D fill:#1565C0,color:#fff

解决方案:凭证交换

核心思路:用一个凭证换另一个凭证。

你的主凭证(X.509-SVID / WIT)虽然 AWS 不认识,但它可以用来向某个凭证交换服务证明你的身份,然后换取 AWS 认识的凭证。

sequenceDiagram
    participant Agent as AI Agent
    participant Exchange as 凭证交换服务
    participant AWS as AWS STS
    participant KMS as AWS KMS

    Agent->>Agent: 1. 持有主凭证
(X.509-SVID / WIT) Agent->>Exchange: 2. 用主凭证证明身份
请求 AWS 凭证 Exchange->>Exchange: 3. 验证主凭证 Exchange->>AWS: 4. 调用 AssumeRoleWithWebIdentity AWS->>Exchange: 5. 返回 AWS STS Token Exchange->>Agent: 6. 返回 AWS STS Token Agent->>KMS: 7. 使用 AWS STS Token 访问 KMS->>Agent: 8. 返回数据

实际例子

  1. Agent 持有 WIT(WIMSE 工作负载身份令牌)
  2. 调用 AWS STS 的 AssumeRoleWithWebIdentity API,传入 WIT
  3. AWS 验证 WIT(可能通过 OIDC 联邦)
  4. AWS 返回 AWS STS Token(临时凭证)
  5. Agent 用 AWS STS Token 访问 KMS

标准化的交换机制

机制说明适用场景
OAuth 2.0 Token ExchangeRFC 8693,用一种 Token 换另一种通用跨系统凭证交换
Transaction Tokens限定单次交易的短期 Token微服务调用链
Workload Identity Federation云厂商提供的身份联邦(如 AWS、GCP)云原生工作负载

关键点

  • 主凭证不会被发送到目标服务,只用于向交换服务证明身份
  • 换取的目标凭证是短期的、受限的
  • 凭证交换可以链式进行(A 换 B,B 换 C),但每一步都有审计追踪

Agent 证明(Attestation)

什么是证明?

Agent attestation is the identity-proofing mechanism for AI agents.

证明是 Agent 的"身份核验"过程,类似于人类开户时的身份验证。

证明机制类型

类型说明示例
硬件证明基于 TEE(可信执行环境)证据Intel SGX、AMD SEV
软件完整性代码签名、哈希校验容器镜像签名
供应链来源构建来源验证SLSA provenance
平台证明编排层身份Kubernetes Service Account
操作员断言人工确认管理员批准

证明流程

sequenceDiagram
    participant Agent as AI Agent
    participant Attestor as 证明服务
    participant Issuer as 凭证发放服务

    Agent->>Attestor: 1. 提交证明证据
(平台身份、代码签名等) Attestor->>Attestor: 2. 验证证据 Attestor->>Issuer: 3. 确认身份 Issuer->>Agent: 4. 发放凭证
(绑定到标识符)

SPIFFE 实现:收集工作负载和执行环境信号(如运行位置、平台身份属性),验证后绑定到 SPIFFE 标识符并发放 SVID。

Agent 认证

问题:仅依赖传输层认证的局限

前文提到 Agent 可以持有 X.509-SVID 或 JWT-SVID 作为凭证,通过 mTLS 进行身份认证。但在现实架构中,仅依赖 mTLS 作为认证手段会面临三个关键问题:

问题 1:中间代理(TLS Termination)

在现代架构中,请求通常要经过多层代理。如果 TLS 在某层终止,后续跳转就失去了 mTLS 认证:

flowchart LR
    A[Agent
持有 X.509-SVID] -->|"mTLS
身份验证"| B[负载均衡器] B -->|"TLS 在此终止
身份丢失"| C[API Gateway] C -->|"普通 HTTP
无身份"| D[微服务 A] D -->|"普通 HTTP
无身份"| E[微服务 B] style A fill:#228B22,color:#fff style B fill:#B22222,color:#fff style C fill:#B22222,color:#fff

后果:微服务 A 和 B 无法验证请求来自哪个 Agent,只知道"经过了 Gateway"。

问题 2:Serverless 环境

在 AWS Lambda、Azure Functions 等环境中,TLS 由云厂商管理,你的代码拿不到客户端证书

flowchart LR
    A[Agent] -->|"mTLS"| B[AWS API Gateway]
    B -->|"AWS 管理 TLS"| C[Lambda 函数]
    
    C -->|"❌ 拿不到
客户端证书"| D[无法验证身份] style A fill:#228B22,color:#fff style B fill:#1565C0,color:#fff style D fill:#B22222,color:#fff

后果:Lambda 只能依赖 API Gateway 传递的身份信息(如 header),如果 header 被伪造,验证就会失效。

问题 3:跨域拓扑

当请求需要跨越多个信任域时,端到端的身份传递变得非常困难:

flowchart LR
    subgraph 你的数据中心["信任域 A:你的数据中心"]
        A1[Agent]
        A2[服务 A]
        CA1["CA: 你的根证书"]
    end

    subgraph AWS环境["信任域 B:AWS"]
        B1[AWS 服务]
        CA2["CA: AWS 证书"]
    end

    subgraph 第三方环境["信任域 C:第三方"]
        C1[第三方 API]
        CA3["CA: 第三方证书"]
    end

    A1 -->|"mTLS ✓"| A2
    A2 -.->|"证书不被信任
???"| B1 B1 -.->|"证书不被信任
???"| C1 A1 -.- CA1 A2 -.- CA1 B1 -.- CA2 C1 -.- CA3 style A1 fill:#228B22,color:#fff style CA1 fill:#1565C0,color:#fff style CA2 fill:#B22222,color:#fff style CA3 fill:#B22222,color:#fff

问题本质

信任域证书颁发者谁信任谁
你的数据中心你的 CA你的服务信任你的 CA
AWSAWS CAAWS 服务信任 AWS CA
第三方第三方 CA第三方信任自己的 CA

端到端身份传递的困难

  1. Agent 的证书在你的 CA 签发 → AWS 不信任你的 CA
  2. 即使 AWS 允许联邦 → 需要额外的信任配置,且无法传递到第三方
  3. 每跨越一个域,身份连续性就断裂一次

后果:Agent 的 X.509 证书只能在自己的信任域内使用,无法实现端到端的身份传递。

解决方案:双层认证架构

既然传输层(mTLS)有上述局限,IETF Draft 提出在应用层增加一层认证,形成纵深防御:

flowchart TB
    subgraph 传输层["传输层认证"]
        T1[mTLS
双向证书验证] end subgraph 应用层["应用层认证"] A1[WIMSE Proof Tokens] A2[HTTP Message Signatures] end T1 --> A1 T1 --> A2 style T1 fill:#1565C0,color:#fff style A1 fill:#228B22,color:#fff style A2 fill:#228B22,color:#fff

核心思路

场景传输层能解决?应用层能解决?
TLS 终止后身份丢失✅(Token 随请求传递)
Serverless 拿不到证书✅(从 Header 读取)
跨域身份传递✅(Token 可跨域验证)

三层机制的完整流程

问题:传输层和两个应用层认证机制到底是什么关系?

答案:它们是互补的纵深防御机制,按顺序组合使用:

sequenceDiagram
    participant Agent as AI Agent
    participant TLS as TLS 层
    participant App as 应用层
    participant Server as 服务端

    Note over Agent: 准备阶段:持有 X.509-SVID + WIT + 私钥

    rect rgb(21, 101, 192)
        Note over Agent,TLS: ① 传输层认证(mTLS)
        Agent->>TLS: 发起 TLS 连接
携带 X.509-SVID TLS->>TLS: 双向证书验证
Agent 证书 ↔ 服务端证书 TLS->>Agent: TLS 通道建立 end Note over Agent: 构造 HTTP 请求:
• Digest: 内容哈希
• Workload-Identity-Token: WIT
• Proof-Token: WPT(用私钥签名)
• Authorization: HTTP Signature rect rgb(34, 139, 34) Note over App,Server: ② 应用层认证 1:WPT Agent->>App: HTTP 请求(携带 WPT) App->>App: 解析 WPT
验证签名匹配 WIT App->>App: ✓ 证明持有私钥 end rect rgb(34, 139, 34) Note over App,Server: ③ 应用层认证 2:HTTP Signature App->>App: 验证 HTTP Signature
检查方法、路径、内容哈希 App->>App: ✓ 请求未被篡改 end App->>Server: 三层验证全部通过 Server->>Agent: 处理请求,返回数据

分层职责

层次机制验证什么防御什么
传输层mTLS证书合法性、双向身份中间人攻击、网络层窃听
应用层 1WPT持有 WIT 对应的私钥证书被盗用(需要私钥才能生成 WPT)
应用层 2HTTP Signature请求完整性(方法、路径、内容)请求篡改、重放攻击

攻击场景覆盖

攻击场景mTLS 能防御?WPT 能防御?HTTP Signature 能防御?
中间人窃听--
伪造证书--
证书被盗用✅(需要私钥)✅(需要私钥)
请求被篡改❌(TLS 终止后)✅(签名验证会失败)
重放攻击✅(时间戳 + nonce)

组合策略

  1. 理想情况:mTLS + WPT + HTTP Signature(三层都启用)
  2. TLS 会终止:WPT + HTTP Signature(跳过 mTLS)
  3. 简单场景:仅 mTLS(如果不需要端到端身份传递)

传输层:mTLS

优势

  • 建立安全通道同时完成身份验证
  • 适用于服务网格(Istio、Linkerd)
  • 简化授权决策(传输身份与应用请求关联)

适用场景:全链路可控、同一信任域内的服务间认证。

应用层 1:WIMSE Proof Tokens (WPT)

WPT 是协议无关的应用层认证机制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "typ": "workload-proof-token",
  "alg": "ES256",
  "kid": "agent-key-1"
}
{
  "aud": "https://api.example.com",
  "exp": 1710000000,
  "jti": "unique-token-id",
  "wth": "sha256-hash-of-wit",
  "txn_hash": "sha256-hash-of-transaction-token"
}

关键特性

  • Proof of Possession:证明持有 WIT 对应的私钥
  • 绑定消息上下文:与 HTTP 请求绑定,防止重放
  • 短期有效:减少重放攻击窗口
  • 协议无关:可扩展到 Kafka、gRPC 等

应用层 2:HTTP Message Signatures

基于 HTTP Message Signatures 标准:

1
2
3
4
5
6
7
POST /api/execute HTTP/1.1
Host: api.example.com
Content-Type: application/json
Digest: sha-256=base64-hash
Authorization: Signature keyId="agent-key",algorithm="ecdsa-p256-sha256",
  signature=base64-signature
Workload-Identity-Token: eyJ...

必须签名的组件

  • HTTP 方法
  • 请求目标(request-target)
  • 内容摘要(Content-Digest)
  • WIT 本身

Agent 授权

为什么需要授权?

认证解决了"你是谁"的问题,但你是谁并不代表你能做什么

AI Agent 在执行任务时,需要访问各种资源:

  • 查询用户的银行账户
  • 调用第三方 API
  • 访问内部数据库
  • 操作云服务

这些操作都需要授权——明确 Agent 被允许做什么、不被允许做什么。

核心问题

问题说明
代理谁操作Agent 是代表自己,还是代表用户?
能做什么Agent 能访问哪些资源?能执行哪些操作?
范围多大授权是全量的,还是限定范围的?
多久有效授权是长期的,还是短期的?

授权场景分类

根据代理谁操作,IETF Draft 将授权场景分为三类:

flowchart TB
    A[Agent 授权场景]

    A --> B["场景 1:代表用户操作
(委托授权)"] A --> C["场景 2:代表自己操作
(自主授权)"] A --> D["场景 3:被其他系统调用
(Agent 作为资源)"] B --> B1["用户 → Agent → 用户资源"] C --> C1["Agent → Agent 自己的资源"] D --> D1["其他系统 → Agent → Agent 提供的服务"] style A fill:#1565C0,color:#fff style B fill:#228B22,color:#fff style C fill:#228B22,color:#fff style D fill:#228B22,color:#fff

场景对比

场景谁授权Agent 代表谁典型例子
委托授权用户用户Agent 帮用户查询银行账户
自主授权Agent 自己Agent 自己Agent 定期清理自己的缓存
被调用调用方系统调用方客服 Agent 被订单系统调用

选型决策

你的场景选择理由
Agent 需要访问用户的私人数据(邮件、文件)委托授权必须有用户明确授权
Agent 执行定时任务(清理、备份)自主授权不涉及用户数据
Agent 提供服务给其他系统调用被调用Agent 本身是资源

场景 1:用户委托授权

问题:Agent 需要帮用户执行操作,但用户的数据只有用户自己能授权访问。

具体例子

用户:“帮我查一下我的银行账户余额”

Agent 需要访问银行 API,但银行 API 只允许用户本人访问。

Agent 必须获得用户的授权,才能代表用户访问银行 API。

sequenceDiagram
    participant User as 用户
    participant Agent as AI Agent
    participant AS as 授权服务器
(银行) participant Bank as 银行 API User->>Agent: 1. "帮我查银行余额" Agent->>AS: 2. 发起授权请求
(Authorization Code Flow) AS->>User: 3. 重定向到登录页
"Agent 请求访问您的账户" User->>AS: 4. 登录并点击"允许" AS->>Agent: 5. 返回 Access Token Note over Agent: Token 包含:
• client_id: agent-finance
• sub: user-123
• scope: read:accounts Agent->>Bank: 6. 携带 Token 访问
GET /accounts Bank->>Bank: 7. 验证 Token
确认 user-123 授权了 agent-finance Bank->>Agent: 8. 返回账户数据 Agent->>User: 9. "您的余额是..."

Access Token 结构

1
2
3
4
5
6
7
{
  "client_id": "agent-finance-analyzer",
  "sub": "user-123",
  "aud": "https://api.bank.com",
  "scope": "read:accounts",
  "exp": 1710000000
}

关键点

  • sub(主体):被代理的用户身份
  • client_id:Agent 身份
  • scope:授权范围(只能读账户,不能转账)

选型建议

需求使用
涉及用户私人数据✅ 委托授权
用户需要明确同意✅ 委托授权
需要审计"谁授权了什么"✅ 委托授权
自动化定时任务❌ 不适合,用自主授权

场景 2:Agent 自主授权

问题:Agent 执行的任务不涉及用户数据,不需要用户授权。

具体例子

Agent 每天凌晨自动清理过期缓存、上报健康状态。

这些操作不涉及用户数据,Agent 可以用自己的身份获取授权。

sequenceDiagram
    participant Agent as AI Agent
    participant AS as 授权服务器
    participant API as 内部 API

    Note over Agent: 定时任务触发
(每天凌晨 2:00) Agent->>Agent: 1. 使用自己的凭证
(X.509-SVID 或 JWT) Agent->>AS: 2. Client Credentials Grant
或 JWT Authorization Grant AS->>AS: 3. 验证 Agent 身份
(不是用户身份) AS->>Agent: 4. 返回 Access Token Note over Agent: Token 包含:
• client_id: agent-health-monitor
• scope: write:cache Agent->>API: 5. 携带 Token 访问
DELETE /cache/expired API->>Agent: 6. 返回结果

Access Token 结构

1
2
3
4
5
6
7
{
  "client_id": "agent-health-monitor",
  "sub": "agent-health-monitor",
  "aud": "https://api.internal.com",
  "scope": "write:cache read:health",
  "exp": 1710000000
}

关键点

  • subclient_id 相同:Agent 代表自己
  • 没有"用户授权"环节

选型建议

需求使用
定时任务、后台作业✅ 自主授权
不涉及用户数据✅ 自主授权
Agent 维护自己的资源✅ 自主授权
需要访问用户私人数据❌ 不适合,用委托授权

⚠️ 注意:Agent 必须使用前文定义的工作负载身份机制(SPIFFE/WIMSE),不能使用静态长期密钥

场景 3:Agent 被其他系统调用

问题:Agent 本身可以作为一个服务被其他系统(或其他 Agent)调用。

具体例子

订单系统收到用户投诉,需要调用客服 Agent 自动处理。

此时 Agent 是"被调用方",需要验证调用者的授权。

sequenceDiagram
    participant Order as 订单系统
    participant AS as 授权服务器
    participant Agent as 客服 Agent
    participant KB as 知识库

    Order->>AS: 1. 获取 Access Token
(代表订单系统) AS->>Order: 2. 返回 Token Order->>Agent: 3. 调用 Agent API
Authorization: Bearer token Agent->>AS: 4. 验证 Token
(Token 是订单系统的) AS->>Agent: 5. 返回验证结果
订单系统有调用权限 Agent->>KB: 6. 查询知识库 KB->>Agent: 7. 返回相关文章 Agent->>Order: 8. 返回处理结果

关键点

  • Agent 作为 OAuth 受保护资源
  • 需要验证调用者的 Access Token
  • 可以根据 Token 中的 client_id 做权限控制

选型建议

需求使用
Agent 提供 API 给其他系统✅ 被调用场景
需要控制谁能调用 Agent✅ 被调用场景
Agent 自己主动执行任务❌ 不适合,用场景 1 或 2

进阶:Transaction Tokens 降低风险

问题:Access Token 在微服务间传递时,会增加泄露和重放风险。

具体例子

1
2
3
4
5
Agent → API Gateway → 订单服务 → 支付服务 → 银行服务
        Access Token 在整个链路中传递
        如果支付服务被攻破,攻击者拿到 Token
        可以用它做任何被授权的操作

解决方案Transaction Tokens(交易令牌)

sequenceDiagram
    participant Agent as AI Agent
    participant Gateway as API 网关
    participant Order as 订单服务
    participant Pay as 支付服务

    Agent->>Gateway: 1. Access Token
scope: read:accounts,write:transfers Gateway->>Gateway: 2. 验证 Access Token Gateway->>Gateway: 3. 生成 Transaction Token
scope: 仅限本次交易
context: 订单 #12345
金额: ¥500 Gateway->>Order: 4. Transaction Token Order->>Pay: 5. 传递 Transaction Token Pay->>Pay: 6. 验证 Token
✓ 订单 #12345 有效
✓ 金额匹配 Note over Pay: 如果 Token 被盗用
用于其他订单
验证会失败

Transaction Token 特性

特性说明例子
Downscoped权限被缩小原 Token 能读写,新 Token 只能查订单 #12345
Context-bound绑定交易上下文订单号、金额、操作类型
Short-lived短期有效通常几分钟内过期
Non-reusable无法复用修改任何参数都会导致验证失败

对比

维度Access TokenTransaction Token
权限范围完整授权范围缩小到单次交易
上下文绑定绑定订单号、金额等
复用风险可能被复用无法复用
泄露影响大(完整权限)小(仅限本次交易)

进阶:跨域访问

问题:Agent 需要访问不同授权服务器保护的资源。

具体例子

你的公司有内部授权服务器,AWS 有自己的授权服务器。

Agent 持有内部系统的 Access Token,但需要访问 AWS KMS。

sequenceDiagram
    participant Agent as AI Agent
    participant IntAS as 内部授权服务器
    participant AWSSTS as AWS STS
    participant KMS as AWS KMS

    Note over Agent: 步骤 1-2: 获取内部 Token
    Agent->>IntAS: 1. 请求内部 Access Token
    IntAS->>Agent: 2. 返回 Access Token A

    Note over Agent: 步骤 3-5: 跨域换取 AWS Token
    Agent->>AWSSTS: 3. AssumeRoleWithWebIdentity
携带 Access Token A
(作为身份断言) AWSSTS->>IntAS: 4. 验证身份断言
GET /.well-known/openid-configuration IntAS->>AWSSTS: 5. 返回配置信息 AWSSTS->>Agent: 6. 返回 AWS STS Token Note over Agent: 步骤 7-8: 使用 AWS Token Agent->>KMS: 7. 携带 AWS STS Token KMS->>Agent: 8. 返回数据

核心机制Token Exchange / Identity Federation

步骤说明
1-2Agent 用自己的凭证换取内部系统 Access Token
3Agent 将内部 Token 作为"身份断言"发送给 AWS
4-5AWS 验证身份断言(通过 OIDC 发现机制)
6AWS 返回自己的临时凭证
7-8Agent 用 AWS 凭证访问 AWS 资源

两种机制

机制适用场景说明
OAuth Identity Chaining通用跨域用一种 Token 换另一种
Identity Assertion JWT Grant有 SAML/OIDC 断言优化流程,减少请求

进阶:Human in the Loop

问题:Agent 在执行敏感操作时,可能需要用户实时确认。

具体例子

Agent:“我找到了最优的转账方案,准备转账 ¥5000,是否确认?”

用户:“确认”

Agent 继续执行转账。

关键误区UI 确认不等于正式授权

flowchart LR
    A[Agent 请求确认] --> B[用户点击确认]
    B --> C{这算授权吗}
    C -->|不算| D[只是 UI 交互
无法审计] C -->|正确做法| E[绑定到授权服务器的
正式授权凭证] style D fill:#B22222,color:#fff style E fill:#228B22,color:#fff

正确做法CIBA(Client Initiated Backchannel Authentication)

sequenceDiagram
    participant Agent as AI Agent
    participant AS as 授权服务器
    participant User as 用户手机 App

    Agent->>AS: 1. 发起 CIBA 请求
需要用户确认转账 5000 元 AS->>User: 2. 推送通知
Agent 请求转账授权 Note over Agent: 继续执行其他任务
不阻塞 User->>User: 3. 查看详情
确认金额、收款人 User->>AS: 4. 批准或拒绝 AS->>AS: 5. 生成正式授权凭证 AS-->>Agent: 6. 返回授权结果
可审计的授权记录 Agent->>Agent: 7. 执行转账

CIBA 的优势

特性UI 确认CIBA
可审计
防伪造
绑定授权凭证
非阻塞取决于实现

反模式:工具到服务的访问

问题:Agent 调用 Tool,Tool 需要访问 Service。能否直接转发 Agent 的 Access Token?

❌ 反模式

flowchart LR
    A[Agent] -->|"Access Token
scope: read:accounts"| T[Tool] T -->|"直接转发同一 Token"| S[Service] style T fill:#B22222,color:#fff Note1["问题:
1. Tool 拿到了用户的完整权限
2. Token 可能被 Tool 滥用
3. 无法区分 Agent 和 Tool 的操作"] T -.- Note1

具体例子

Agent 调用 Payment Tool 帮用户转账。

Agent 的 Access Token 权限是 read:accounts,write:transfers

如果 Tool 直接转发这个 Token,Tool 就能用它做任何操作,包括查询用户所有账户。

✅ 正确做法Token Exchange

sequenceDiagram
    participant Agent as AI Agent
    participant Tool as Payment Tool
    participant AS as 授权服务器
    participant Bank as 银行 API

    Agent->>Tool: 1. 调用 Tool
携带 Access Token
scope: read:accounts,write:transfers Tool->>AS: 2. Token Exchange
请求缩小权限的 Token
scope: write:transfers AS->>AS: 3. 验证原 Token
发放缩小权限的 Token AS->>Tool: 4. 返回新的 Access Token
scope: write:transfers only Tool->>Bank: 5. 使用新 Token 访问 Bank->>Tool: 6. 返回结果 Tool->>Agent: 7. 返回结果 Note over Tool,Bank: Tool 只能转账
无法读取账户信息

Token Exchange 请求示例

1
2
3
4
5
6
7
8
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<原 Token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&scope=write:transfers

为什么要这样做?

维度直接转发Token Exchange
Tool 权限与 Agent 相同被缩小
审计追踪无法区分 Agent 和 Tool清晰区分
安全风险Tool 被攻破 = Agent 权限泄露影响被限制
最小权限

总结

It is an anti-pattern for Tools to forward access tokens it received from the Agent to Services or Resources.

Tool 应该通过 Token Exchange 获取自己需要的最小权限 Token,而不是转发 Agent 的完整权限 Token。

运维与可观测性

问题:动态环境下的配置管理

在 AI Agent 的实际部署中,环境往往是动态变化的:

  • 新的授权服务器上线
  • 新的受保护资源加入
  • Agent 实例动态扩缩容
  • 策略频繁更新

如果每个变化都需要手动修改配置,运维成本会非常高。

解决方案 1:OAuth 2.0 动态发现

核心思路:通过标准化的元数据端点,让 Agent 动态发现如何与授权系统交互。

授权服务器发现

问题:Agent 需要知道:

  • 去哪里获取 Token?
  • 支持哪些授权方式?
  • 公钥在哪里?

解决方案/.well-known/oauth-authorization-server

1
2
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: auth.example.com

响应示例

1
2
3
4
5
6
7
8
{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/token",
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "grant_types_supported": ["authorization_code", "client_credentials"],
  "token_endpoint_auth_methods_supported": ["private_key_jwt", "tls_client_auth"]
}

Agent 使用方式

  1. 请求 .well-known 端点
  2. 解析 JSON,找到需要的端点
  3. 无需硬编码配置

受保护资源发现

问题:Agent 需要访问某个 API,但不知道:

  • 这个 API 受哪个授权服务器保护?
  • 需要什么 scope?
  • Token 应该怎么传递?

解决方案:Protected Resource Metadata

1
2
GET /.well-known/oauth-protected-resource HTTP/1.1
Host: api.example.com

响应示例

1
2
3
4
5
6
{
  "resource": "https://api.example.com",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["read", "write"],
  "bearer_methods_supported": ["header"]
}

Agent 工作流

  1. 请求 API 的 .well-known 端点
  2. 发现需要去 auth.example.com 获取 Token
  3. 知道需要 readwrite scope
  4. 知道 Token 放在 HTTP Header 中

客户端能力发现

问题:授权服务器需要知道客户端(Agent)的能力:

  • 支持哪些认证方式?
  • 回调地址是什么?
  • 公钥在哪里?

解决方案:Client ID Metadata Documents

client_id 是 HTTPS URL 时:

1
2
3
4
5
6
{
  "client_id": "https://agent.example.com/.well-known/oauth-client",
  "redirect_uris": ["https://agent.example.com/callback"],
  "token_endpoint_auth_method": "private_key_jwt",
  "jwks_uri": "https://agent.example.com/.well-known/jwks.json"
}

优势:授权服务器可以动态发现客户端能力,无需预注册。

解决方案 2:动态监控与补救

为什么需要动态监控?

AI Agent 的行为具有不可预测性

  • 由 LLM 决策,行为可能偏离预期
  • 长时间运行,权限可能累积
  • 跨多个系统操作,难以追踪

静态的授权决策(“一次性授权”)无法应对这些变化。

可观测性信号

信号类型监控什么示例
日志授权决策、访问记录“Agent A 访问了资源 B,scope: read”
指标请求频率、错误率、延迟“过去 1 小时,Agent A 请求了 1000 次 API”
追踪跨服务调用链“请求经过 Gateway → Service A → Service B”

具体例子

1
2
3
4
5
6
时间: 2026-03-25 14:30:00
Agent: spiffe://production/agent/finance-analyzer
操作: GET /api/accounts
资源: 用户银行账户
决策: ✓ 允许(scope: read:accounts)
响应时间: 120ms

动态授权调整(补救)

基于观察到的行为和系统状态,实时修改授权决策

flowchart LR
    A[可观测性信号] --> B{策略引擎评估}
    B -->|正常| C[保持当前授权]
    B -->|异常访问模式| D[缩小 scope
或撤销 Token] B -->|资源过度使用| E[限流或暂停访问] B -->|安全事件| F[立即撤销凭证] B -->|策略变更| G[重新评估授权] style C fill:#228B22,color:#fff style D fill:#B22222,color:#fff style E fill:#E65100,color:#fff style F fill:#B22222,color:#fff style G fill:#1565C0,color:#fff

具体场景

触发条件检测方式补救措施例子
异常访问模式短时间内大量请求缩小 scope 或撤销 TokenAgent 突然请求从未访问过的敏感 API
资源过度使用超过配额限流或暂停访问Agent 调用 API 超过 1000 次/小时
安全事件检测到攻击特征立即撤销凭证Agent 行为被判定为恶意
策略变更策略配置更新重新评估授权公司规定不再允许 Agent 访问某个 API

实现示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 伪代码:动态授权决策
def evaluate_authorization(agent_id, resource, action):
    # 1. 检查基础权限
    base_decision = check_policy(agent_id, resource, action)
    
    # 2. 检查实时行为
    behavior_score = analyze_behavior(agent_id)
    
    # 3. 检查资源使用
    usage = get_resource_usage(agent_id)
    
    # 4. 动态调整
    if behavior_score < THRESHOLD:
        return DENY  # 异常行为,拒绝
    
    if usage > QUOTA:
        return THROTTLE  # 超过配额,限流
    
    return base_decision

闭环:监控 → 分析 → 补救 → 验证

sequenceDiagram
    participant Agent as AI Agent
    participant Gateway as API Gateway
    participant Monitor as 监控系统
    participant Policy as 策略引擎

    Agent->>Gateway: 1. 请求访问资源
    Gateway->>Monitor: 2. 记录访问日志
    Monitor->>Monitor: 3. 分析行为模式
    
    alt 正常
        Monitor->>Gateway: 4a. 无需干预
        Gateway->>Agent: 5a. 允许访问
    else 异常
        Monitor->>Policy: 4b. 触发告警
        Policy->>Policy: 5b. 评估补救措施
        Policy->>Gateway: 6b. 更新授权策略
        Gateway->>Agent: 7b. 拒绝或限流
    end

安全与隐私考虑

凭证安全

要求问题解决方案
短期凭证长期凭证泄露影响大凭证有效期 1-24 小时,自动轮换
自动轮换手动管理负担重SPIFFE Workload API 自动轮换
硬件保护私钥可能被窃取TPM、安全飞地保护私钥
最小权限过度授权风险只请求必要的 scope

具体例子

1
2
3
4
5
❌ 反模式:
API Key: sk-prod-xxxx(永久有效,硬编码在代码中)

✅ 正确做法:
X.509-SVID: 有效期 1 小时,自动轮换,存储在内存中

认证安全

机制防护什么如何工作
mTLS中间人攻击、伪造身份双向证书验证
WPT证书被盗用证明持有私钥(PoP)
HTTP Signatures请求篡改对请求签名,验证完整性

授权安全

机制防护什么如何工作
Transaction TokensToken 被滥用缩小权限到单次交易
短期 Access TokenToken 泄露1 小时过期,减少泄露窗口
Audience 限制Token 转发Token 只能用于特定服务

隐私保护

问题:Token 可能泄露敏感信息

Access Token 和 ID Token 可能包含:

  • 用户身份(subnameemail
  • 组织信息(tenant_iddepartment
  • 权限范围(scope

这些信息在传输和存储过程中可能泄露。

解决方案

策略问题解决方案
最小化声明Token 包含过多信息只包含必要的声明
Opaque TokenToken 内容可被解析使用随机字符串,内容在授权服务器查询
短期 TokenToken 长期有效1 小时过期,减少关联风险
不记录完整 Token日志泄露只记录 Token ID 或哈希
Downscoped Token传播完整权限使用 Transaction Token 传播缩小权限

具体例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ❌ 过多信息
{
  "sub": "user-123",
  "name": "张三",
  "email": "zhangsan@company.com",
  "department": "财务部",
  "salary_level": "P8"
}

// ✅ 最小化信息
{
  "sub": "user-123",
  "scope": "read:accounts"
}

跨域隐私

问题:Agent 代表用户访问多个域时,用户身份信息可能泄露到不相关的服务。

原则

Implementations MUST ensure that user identity information delegated to agents is not exposed to unrelated services.

解决方案

  • 每个域使用独立的 Token(通过 Token Exchange)
  • 不在 Token 中传递敏感身份信息
  • 跨域时使用匿名标识符

总结

与 OpenID Foundation 白皮书的关系

这份 IETF Draft 与前文解读的《Identity Management for Agentic AI》白皮书高度互补:

维度OpenID Foundation 白皮书IETF Draft
定位问题识别与用例分析技术框架与标准组合
深度概念性,面向决策者技术性,面向实现者
核心标准OAuth 2.1、CIBA、MCPWIMSE、SPIFFE、OAuth 2.0
创新点Agent Payments、KYAPayTransaction Tokens、AIMS 模型

共同观点

  1. 不发明新协议 —— 基于现有成熟标准
  2. 委托优于冒充 —— 明确区分 Agent 身份与用户身份
  3. 短期凭证 —— 自动轮换,减少泄露风险
  4. 可观测性 —— 动态监控和补救

核心架构

flowchart LR
    subgraph 身份层["身份层"]
        I1[SPIFFE/WIMSE 标识符]
        I2[X.509/JWT/WIT 凭证]
        I3[Attestation 证明]
    end

    subgraph 认证层["认证层"]
        A1[mTLS 传输层]
        A2[WPT/HTTP Signature 应用层]
    end

    subgraph 授权层["授权层"]
        O1[OAuth 2.0 Access Token]
        O2[Transaction Token]
        O3[Token Exchange]
    end

    subgraph 运维层["运维与可观测性"]
        M1[动态发现]
        M2[监控与告警]
        M3[动态补救]
    end

    身份层 --> 认证层 --> 授权层 --> 运维层

    style I1 fill:#228B22,color:#fff
    style I2 fill:#228B22,color:#fff
    style I3 fill:#228B22,color:#fff
    style A1 fill:#1565C0,color:#fff
    style A2 fill:#1565C0,color:#fff
    style O1 fill:#5E35B1,color:#fff
    style O2 fill:#5E35B1,color:#fff
    style O3 fill:#5E35B1,color:#fff
    style M1 fill:#B22222,color:#fff
    style M2 fill:#B22222,color:#fff
    style M3 fill:#B22222,color:#fff

关键结论

  1. Agent 是 Workload —— 使用工作负载身份而非用户身份
  2. 短期凭证 + 自动轮换 —— 消除 API Key 反模式
  3. 双层认证 —— mTLS(传输层) + WPT/Signatures(应用层)
  4. OAuth 2.0 是基础 —— 委托授权、Token Exchange、Transaction Token
  5. Human in the Loop —— UI 确认 ≠ 正式授权,需要 CIBA 等机制
  6. 动态发现与监控 —— 减少静态配置,适应动态环境,实时补救