概述
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. 返回用户数据
关键点:
- Authorization Code 是一次性的、短期的中间凭证
- Access Token 是最终的授权凭证,包含权限范围(scope)
- 用户密码只在授权服务器上输入,从不暴露给第三方应用
为什么需要 Authorization Code?
你可能会问:为什么不在用户登录授权后,直接返回 access_token,还要再用 authorization_code 换一次?
这是 OAuth 2.0 的核心安全设计。
直接返回 Token 的风险(Implicit Flow)
如果直接在浏览器重定向 URL 中返回 Token:
| |
问题:
| 风险 | 说明 |
|---|---|
| 浏览器历史记录 | Token 会被记录在浏览器历史中,任何人查看历史都能看到 |
| Referer 泄露 | 如果页面加载了第三方资源(图片、脚本),Token 可能通过 Referer 请求头泄露给第三方 |
| 恶意脚本 | 浏览器扩展、注入的恶意脚本可能读取 URL 中的 Token |
| 服务器日志 | 代理服务器、负载均衡器、CDN 可能记录完整 URL(包含 Token) |
| 转发泄露 | 用户复制 URL 分享给别人,Token 一起泄露 |
Authorization Code 的安全作用
| |
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 存储到 Cookie 安全吗?
问题:既然 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
| 维度 | Cookie | URL(历史记录) |
|---|---|---|
| 自动发送 | ✅ 仅对目标域 | ❌ 可被复制到任何地方 |
| 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 安全配置示例:
| |
现代方案: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_challenge:code_verifier的 SHA256 哈希值,在授权请求 URL 中公开传输code:授权服务器返回的授权码,在重定向 URL 中公开传输
攻击场景分析
攻击者能截获什么?
| 数据 | 传输方式 | 攻击者能否截获 | 说明 |
|---|---|---|---|
code_challenge | 授权请求 URL | ✅ 能 | 浏览器历史记录、代理日志 |
code | 重定向 URL | ✅ 能 | 浏览器历史记录、代理日志 |
code_verifier | POST 请求体 | ❌ 不能 | 不经过浏览器 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 的安全本质是:用单向哈希函数保护密钥。攻击者能截获
code和code_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 | 签名的 JWT | eyJhbGciOiJSUzI1NiIs... | 透明,可本地验证和解析 |
Opaque Token(不透明令牌):
| |
- 就是一个随机字符串,没有结构
- 资源服务器收到 Token 后,必须调用授权服务器的 introspection 端点验证
- 授权服务器维护 Token 与权限的映射关系
JWT Token(结构化令牌):
| |
- 是一个 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(头部):
| |
alg:签名算法(RS256 = RSA + SHA-256)typ:类型(JWT)
Payload(负载):
| |
client_id:谁拿到了这个 Tokenscope:这个 Token 允许做什么aud:这个 Token 能用于访问哪个服务(Audience)exp:过期时间(Unix 时间戳)iat:签发时间(Issued At)
Signature(签名):
| |
- 用授权服务器的私钥签名
- 资源服务器用公钥验证签名
关键问题: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:
| |
id_token 是一个 JWT,包含用户的身份声明:
| |
| 声明 | 含义 |
|---|---|
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
详细步骤:
| |
关键验证点
客户端必须验证以下内容:
| 验证项 | 说明 | 失败后果 |
|---|---|---|
| 签名 | 用公钥验证 Token 未被篡改 | Token 可能是伪造的 |
| iss(签发者) | 必须是预期的授权服务器 | Token 来自不受信任的源 |
| aud(受众) | 必须是自己的 client_id | Token 发给别人,被你误用了 |
| 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_token | Access Token |
|---|---|---|
| 用途 | 身份认证(“你是谁”) | 授权访问资源(“能做什么”) |
| 格式 | 必须是 JWT | 可能是 Opaque 或 JWT |
| 谁能解析 | 客户端(你的应用) | 资源服务器(API) |
| 包含什么 | 用户身份信息(sub, name, email) | 权限范围(scope, aud) |
| 如何使用 | 验证后提取用户信息 | 放在 HTTP Header 中访问 API |
| 能否直接使用 | ❌ 必须先验证签名 | ✅ 直接发送给 API |
一句话总结
id_token 是一个签名的 JWT,客户端解码后可以看到用户信息,但必须先用授权服务器的公钥验证签名,确保 Token 未被篡改。
scope 与 openid:理解授权范围
在深入 OIDC 流程之前,需要先理解 scope 和 openid 这两个概念。它们是 OAuth 2.0/OIDC 中最基础但也最容易混淆的部分。
1. 为什么需要 scope?
问题场景:
假设你的应用想访问用户在 Google 的资源:
- 读取用户邮箱
- 读取用户日历
- 读取用户照片
你如何告诉 Google"我想要访问哪些资源"?
传统方式的问题:
| |
每个授权服务器都有自己的接口和参数,应用需要针对每个服务适配。
OAuth 2.0 的解决方案:scope 参数
| |
scope 的作用:
| 作用 | 说明 |
|---|---|
| 声明意图 | 告诉授权服务器"我想要什么权限" |
| 用户可见 | 授权页面显示应用请求的权限(“此应用想要读取你的邮箱”) |
| 权限隔离 | 用户可以选择性授权(允许读取邮箱,但拒绝读取照片) |
| 审计追踪 | Token 中记录授权范围,便于审计 |
2. scope 的实际格式
直截了当地说:scope 就是空格分隔的字符串。
| |
关键点:
| 问题 | 答案 |
|---|---|
| scope 是 JSON 吗? | 不是,就是简单的字符串 |
| scope 是数组吗? | 不是,是空格分隔的字符串 |
| 如何在 URL 中表示? | 用 + 或 %20 表示空格 |
每个 scope 的命名由授权服务器定义,没有统一标准:
| 授权服务器 | scope 示例 |
|---|---|
openid, profile, email, https://www.googleapis.com/auth/gmail.readonly | |
| GitHub | repo, user, read:org |
| AWS | aws.cognito.signin.user.admin |
| 自定义 | 你可以定义任何字符串,如 read:accounts:12345 |
命名约定:
虽然命名没有标准,但常见模式有:
| |
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+email | id_token + access_token |
| OAuth 2.0 请求 | scope=photos.read+email | 只有 access_token |
完整授权请求示例:
| |
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 Token | IETF Draft 中提到,绑定到单次交易 | 转账、敏感操作 |
| 自定义 scope | 授权服务器支持动态 scope | 特定资源访问(需授权服务器支持) |
方案 1:Resource 参数
| |
方案 2:Rich Authorization Requests (RAR)
| |
RAR 允许表达"向账户 12345 发起 500 美元的支付请求"这种复杂权限。
方案 3:Transaction Token
Token 中嵌入交易上下文:
| |
这个 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. 返回数据
流程说明:
- 客户端认证:客户端使用预置的 RSA 私钥对请求参数(audience + timestamp + nonce)签名
- 获取 id_token:调用
/generate-token,服务端验证签名后直接发放 id_token - 换取 AWS 凭证:客户端调用 AWS STS
AssumeRoleWithWebIdentity,传入 id_token - AWS 验证:AWS 请求 OIDC 服务器的
/.well-known/openid-configuration和/keys端点,获取公钥验证 id_token - 获取临时凭证:验证通过后,AWS 返回临时凭证(有效期通常 1 小时)
- 访问资源:客户端使用临时凭证访问 AWS 服务(如 KMS、Secrets Manager)
id_token 的实际结构(oidc.cipherhub.cloud 实现示例)
以下是 oidc.cipherhub.cloud 的 /generate-token 端点返回的 id_token 的实际结构:
JWT Header:
| |
JWT Payload:
| |
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 编码后):
| |
说明:以上结构基于
oidc.cipherhub.cloud的实际实现,作为简化版 OIDC 的参考示例。完整实现代码可参考项目源码。
id_token 的实际结构(oidc.cipherhub.cloud 实现示例)
以下是 oidc.cipherhub.cloud 的 /generate-token 端点返回的 id_token 的实际结构:
JWT Header:
| |
JWT Payload:
| |
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 编码后):
| |
说明:以上结构基于
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 是实现这个目标的技术手段:
| |
| 技术 | 时代 | 适用场景 |
|---|---|---|
| SAML 2.0 | 2005 年 | 企业 SSO(AD FS、Shibboleth),依赖浏览器和 XML |
| OIDC | 2014 年 | 互联网/云原生(Google、GitHub),基于 JSON/JWT |
| CAS | 早期 | 校园网 SSO,现在较少使用 |
OIDC 实现 SSO 的原理
当你用 Google 账号登录多个网站时:
第一次登录网站 A:
- 网站重定向到 Google 登录页
- 你输入 Google 密码
- Google 发放 id_token 给网站 A
- 浏览器在 Google 域下记录登录状态(Cookie)
随后登录网站 B:
- 网站重定向到 Google 登录页
- Google 检测到浏览器已有登录 Cookie
- Google 直接发放 id_token 给网站 B,无需再次输入密码
这就是 OIDC 实现 SSO 的机制。
SSO 与 AI Agent 的关系
在 AI Agent 场景中,SSO 的重要性体现在:
Agent 代理用户访问多个系统:
- 用户登录一次
- Agent 可以代表用户访问邮件、日历、文件等多个系统
- 无需在每个系统中单独授权
统一身份上下文:
- Agent 通过 SSO 获取用户的统一身份
- 在不同系统间保持一致的委托关系
用户体验:
- 用户不需要反复确认授权
- 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:工作负载的身份证号
每个工作负载都有一个全局唯一的标识符:
| |
示例:
| |
trust-domain:信任域,定义信任边界(如acme.com)path:工作负载的路径,由组织自行定义
2. Trust Domain:信任边界
Trust Domain 是一个管理边界,代表一组共享信任根的工作负载:
| |
同一个 Trust Domain 内的工作负载互相认证;不同 Trust Domain 之间需要建立联邦。
3. SVID:可验证的身份凭证
SVID(SPIFFE Verifiable Identity Document) 是绑定了 SPIFFE ID 的凭证,有两种形式:
| 类型 | 用途 | 特点 |
|---|---|---|
| X.509-SVID | mTLS、服务间认证 | 短期证书(1-24小时),自动轮换 |
| JWT-SVID | HTTP 头、应用层 | 更短(5-15分钟),方便传递 |
X.509-SVID 结构:
| |
SPIFFE ID 被放在证书的 SAN 扩展中。
JWT-SVID 结构:
| |
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 的对比
| 维度 | OIDC | SPIFFE |
|---|---|---|
| 目标对象 | 用户 | 工作负载(机器) |
| 身份标识 | 用户 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 更加泛化:
| |
WIMSE 标识符格式
| |
与 SPIFFE ID 格式几乎相同,但:
- WIMSE 是更上层的抽象
- SPIFFE 是 WIMSE 的一种具体实现
- WIMSE 可以支持其他格式的凭证和协议
IETF Draft 为什么选择 WIMSE?
这份 IETF Draft 使用 WIMSE 作为 Agent 标识符,原因:
- 标准化进程:WIMSE 正在 IETF 标准化,比 SPIFFE 更"官方"
- 更广泛的适用性:不限定于 SPIFFE 的实现方式
- 向后兼容:SPIFFE ID 可以直接作为 WIMSE ID 使用
Draft 中的规定:
每个 Agent 必须有且只有一个 WIMSE 标识符。 该标识符可以是 SPIFFE ID。
这意味着你可以:
- 直接使用现有的 SPIFFE 基础设施
- 或者实现自己的 WIMSE 兼容系统
WIT:Workload Identity Token
WIMSE 还引入了 WIT(Workload Identity Token),类似于 SPIFFE 的 JWT-SVID:
| |
前置概念小结:如何组合成 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
组合逻辑:
Agent 自己的身份(SPIFFE/WIMSE)
- 每个 Agent 有唯一的 WIMSE/SPIFFE ID
- 持有 X.509-SVID 或 WIT 证明身份
- 通过 mTLS 或 HTTP Signature 认证
Agent 代表用户时的身份(OIDC + OAuth 2.0)
- 用户的 id_token 证明"用户是谁"
- OAuth Access Token 证明"用户授权 Agent 做什么"
细粒度授权(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
交互流程:
- 用户或系统向 Agent 提供初始请求
- Agent 将上下文(系统提示、工具描述、历史输出等)发送给 LLM
- LLM 返回决策,指导 Agent 选择调用哪些工具/服务
- Agent 调用选定的外部端点
- 外部资源返回结果,Agent 可能将其作为新上下文发送给 LLM
- 重复 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) 是主要标识符格式:
| |
SPIFFE 标识符
SPIFFE(Secure Production Identity Framework for Everyone) 是 WIMSE 的成熟实现:
| |
示例:
| |
要求
- 每个 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-SVID | SPIFFE | mTLS 认证 |
| JWT-SVID | SPIFFE | 应用层认证 |
| WIT (Workload Identity Token) | WIMSE | 工作负载身份令牌 |
凭证要求
- 必须短期:包含明确的过期时间
- 加密绑定:凭证与标识符通过公钥绑定
- 可携带属性:信任域、证明证据、工作负载元数据等
二级凭证交换
问题场景:不同环境有不同的凭证格式
前文提到,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. 返回数据
实际例子:
- Agent 持有 WIT(WIMSE 工作负载身份令牌)
- 调用 AWS STS 的
AssumeRoleWithWebIdentityAPI,传入 WIT - AWS 验证 WIT(可能通过 OIDC 联邦)
- AWS 返回 AWS STS Token(临时凭证)
- Agent 用 AWS STS Token 访问 KMS
标准化的交换机制
| 机制 | 说明 | 适用场景 |
|---|---|---|
| OAuth 2.0 Token Exchange | RFC 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 |
| AWS | AWS CA | AWS 服务信任 AWS CA |
| 第三方 | 第三方 CA | 第三方信任自己的 CA |
端到端身份传递的困难:
- Agent 的证书在你的 CA 签发 → AWS 不信任你的 CA
- 即使 AWS 允许联邦 → 需要额外的信任配置,且无法传递到第三方
- 每跨越一个域,身份连续性就断裂一次
后果: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 | 证书合法性、双向身份 | 中间人攻击、网络层窃听 |
| 应用层 1 | WPT | 持有 WIT 对应的私钥 | 证书被盗用(需要私钥才能生成 WPT) |
| 应用层 2 | HTTP Signature | 请求完整性(方法、路径、内容) | 请求篡改、重放攻击 |
攻击场景覆盖:
| 攻击场景 | mTLS 能防御? | WPT 能防御? | HTTP Signature 能防御? |
|---|---|---|---|
| 中间人窃听 | ✅ | - | - |
| 伪造证书 | ✅ | - | - |
| 证书被盗用 | ❌ | ✅(需要私钥) | ✅(需要私钥) |
| 请求被篡改 | ❌(TLS 终止后) | ❌ | ✅(签名验证会失败) |
| 重放攻击 | ❌ | ❌ | ✅(时间戳 + nonce) |
组合策略:
- 理想情况:mTLS + WPT + HTTP Signature(三层都启用)
- TLS 会终止:WPT + HTTP Signature(跳过 mTLS)
- 简单场景:仅 mTLS(如果不需要端到端身份传递)
传输层:mTLS
优势:
- 建立安全通道同时完成身份验证
- 适用于服务网格(Istio、Linkerd)
- 简化授权决策(传输身份与应用请求关联)
适用场景:全链路可控、同一信任域内的服务间认证。
应用层 1:WIMSE Proof Tokens (WPT)
WPT 是协议无关的应用层认证机制:
| |
关键特性:
- Proof of Possession:证明持有 WIT 对应的私钥
- 绑定消息上下文:与 HTTP 请求绑定,防止重放
- 短期有效:减少重放攻击窗口
- 协议无关:可扩展到 Kafka、gRPC 等
应用层 2:HTTP Message Signatures
基于 HTTP Message Signatures 标准:
| |
必须签名的组件:
- 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 结构:
| |
关键点:
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 结构:
| |
关键点:
sub和client_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 在微服务间传递时,会增加泄露和重放风险。
具体例子:
| |
解决方案: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 Token | Transaction 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-2 | Agent 用自己的凭证换取内部系统 Access Token |
| 3 | Agent 将内部 Token 作为"身份断言"发送给 AWS |
| 4-5 | AWS 验证身份断言(通过 OIDC 发现机制) |
| 6 | AWS 返回自己的临时凭证 |
| 7-8 | Agent 用 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 请求示例:
| |
为什么要这样做?
| 维度 | 直接转发 | 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
| |
响应示例:
| |
Agent 使用方式:
- 请求
.well-known端点 - 解析 JSON,找到需要的端点
- 无需硬编码配置
受保护资源发现
问题:Agent 需要访问某个 API,但不知道:
- 这个 API 受哪个授权服务器保护?
- 需要什么 scope?
- Token 应该怎么传递?
解决方案:Protected Resource Metadata
| |
响应示例:
| |
Agent 工作流:
- 请求 API 的
.well-known端点 - 发现需要去
auth.example.com获取 Token - 知道需要
read或writescope - 知道 Token 放在 HTTP Header 中
客户端能力发现
问题:授权服务器需要知道客户端(Agent)的能力:
- 支持哪些认证方式?
- 回调地址是什么?
- 公钥在哪里?
解决方案:Client ID Metadata Documents
当 client_id 是 HTTPS URL 时:
| |
优势:授权服务器可以动态发现客户端能力,无需预注册。
解决方案 2:动态监控与补救
为什么需要动态监控?
AI Agent 的行为具有不可预测性:
- 由 LLM 决策,行为可能偏离预期
- 长时间运行,权限可能累积
- 跨多个系统操作,难以追踪
静态的授权决策(“一次性授权”)无法应对这些变化。
可观测性信号
| 信号类型 | 监控什么 | 示例 |
|---|---|---|
| 日志 | 授权决策、访问记录 | “Agent A 访问了资源 B,scope: read” |
| 指标 | 请求频率、错误率、延迟 | “过去 1 小时,Agent A 请求了 1000 次 API” |
| 追踪 | 跨服务调用链 | “请求经过 Gateway → Service A → Service B” |
具体例子:
| |
动态授权调整(补救)
基于观察到的行为和系统状态,实时修改授权决策:
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 或撤销 Token | Agent 突然请求从未访问过的敏感 API |
| 资源过度使用 | 超过配额 | 限流或暂停访问 | Agent 调用 API 超过 1000 次/小时 |
| 安全事件 | 检测到攻击特征 | 立即撤销凭证 | Agent 行为被判定为恶意 |
| 策略变更 | 策略配置更新 | 重新评估授权 | 公司规定不再允许 Agent 访问某个 API |
实现示例:
| |
闭环:监控 → 分析 → 补救 → 验证
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 |
具体例子:
| |
认证安全
| 机制 | 防护什么 | 如何工作 |
|---|---|---|
| mTLS | 中间人攻击、伪造身份 | 双向证书验证 |
| WPT | 证书被盗用 | 证明持有私钥(PoP) |
| HTTP Signatures | 请求篡改 | 对请求签名,验证完整性 |
授权安全
| 机制 | 防护什么 | 如何工作 |
|---|---|---|
| Transaction Tokens | Token 被滥用 | 缩小权限到单次交易 |
| 短期 Access Token | Token 泄露 | 1 小时过期,减少泄露窗口 |
| Audience 限制 | Token 转发 | Token 只能用于特定服务 |
隐私保护
问题:Token 可能泄露敏感信息
Access Token 和 ID Token 可能包含:
- 用户身份(
sub、name、email) - 组织信息(
tenant_id、department) - 权限范围(
scope)
这些信息在传输和存储过程中可能泄露。
解决方案
| 策略 | 问题 | 解决方案 |
|---|---|---|
| 最小化声明 | Token 包含过多信息 | 只包含必要的声明 |
| Opaque Token | Token 内容可被解析 | 使用随机字符串,内容在授权服务器查询 |
| 短期 Token | Token 长期有效 | 1 小时过期,减少关联风险 |
| 不记录完整 Token | 日志泄露 | 只记录 Token ID 或哈希 |
| Downscoped Token | 传播完整权限 | 使用 Transaction Token 传播缩小权限 |
具体例子:
| |
跨域隐私
问题: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、MCP | WIMSE、SPIFFE、OAuth 2.0 |
| 创新点 | Agent Payments、KYAPay | Transaction Tokens、AIMS 模型 |
共同观点:
- 不发明新协议 —— 基于现有成熟标准
- 委托优于冒充 —— 明确区分 Agent 身份与用户身份
- 短期凭证 —— 自动轮换,减少泄露风险
- 可观测性 —— 动态监控和补救
核心架构
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
关键结论
- Agent 是 Workload —— 使用工作负载身份而非用户身份
- 短期凭证 + 自动轮换 —— 消除 API Key 反模式
- 双层认证 —— mTLS(传输层) + WPT/Signatures(应用层)
- OAuth 2.0 是基础 —— 委托授权、Token Exchange、Transaction Token
- Human in the Loop —— UI 确认 ≠ 正式授权,需要 CIBA 等机制
- 动态发现与监控 —— 减少静态配置,适应动态环境,实时补救