Token Exchange 与 Agent 身份(RFC 8693)
Agent 代表用户调用工具时的身份委托:持用户 token + Agent 服务身份,换取带委托链的短命 token。
Agent 一等身份
在后台「OAuth 客户端」编辑页给 M2M client 填写 Agent 身份(agent_id) 后,该 client 的 client_credentials token 携带 agent_id claim:
{
"sub": "agent_manager",
"aud": "agent_manager",
"agent_id": "agent_007",
"token_type": "client",
"permissions": ["platform:run:create"]
}
client 停用(下线)后:签发立即停止,introspect 返回 active: false,已发 token 最长 15 分钟自然过期。
换取委托 token(OBO)
POST /oauth/token
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=agent_manager
&client_secret=***
&subject_token=<用户的 access_token>
&resource=https://mcp.erp/api # 二选一:RFC 8707 指示器(绝对 URI)→ 新 token 的 aud
&audience=client_wechat # 二选一:client_id 形态受众(下游按 aud=自己 client_id 验票)
&scope=platform:run:create # 可选,与「用户权限 ∪ client 服务权限」取交集
响应:
{
"access_token": "eyJ...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 900
}
委托 token 的 claims:
{
"sub": "42",
"preferred_username": "alice",
"client_id": "agent_manager",
"aud": "https://mcp.erp/api",
"token_type": "delegation",
"act": { "iss": "https://auth.ai-as.cc", "sub": "agent_manager", "agent_id": "agent_007" },
"agent_id": "agent_007"
}
| 语义 | 说明 |
|---|---|
sub | 原用户不变(OBO):资源服务器按“被代表的人“隔离与归因 |
act | RFC 8693 §4.1 委托链,最外层 = 当前 actor;只含身份 claims |
agent_id | 扁平便捷 claim,工具/网关直读 |
aud | resource(RFC 8707,绝对 URI + client 白名单)或 audience(client_id 形态,同受 allowed_resources 白名单约束);二者都未传时继承原 token 的 aud |
permissions | scope 与「用户权限 ∪ client 服务权限(service_permissions)」的交集——服务委托码(如 mp:publish,不绑用户角色)经 client 侧授予;未传 scope 时继承用户权限(不自动附加服务码) |
| TTL | 默认 900 秒(DELEGATION_TOKEN_TTL_SECS 可配),不发 refresh |
前置条件
client 须为机密 client 且 grant_types 含 urn:ietf:params:oauth:grant-type:token-exchange(后台 grant_types 标签中勾选 token-exchange)。
服务委托场景(应用替用户调下游,如 ExoMind 投递 wechat-publish):登录 client 与服务 client 分离——新建专用机密 client(如 client_exomind_svc),配 allowed_resources=["client_wechat"](audience 白名单)与 service_permissions=["mp:publish"](服务委托码,不绑用户角色)。
增量 consent(用户级委托授权,v7 / 2026-09-27 起)
委托策略(admin 在 client 上配 allowed_resources / service_permissions)只回答「这个 client 可不可换」;带 scope 的换票还要回答「这个用户点没点头」。规则:
- exchange 同时带
scope与resource/audience时,逐 scope 校验用户级委托授权记录(delegation_grants:user + client + audience + scope 唯一):任一 scope 无 active 记录 →403 {"error": "consent_required", "error_description": "用户未授权该服务委托(...);请先在授权确认后重试"}。 - 无 scope / 无 audience 的存量 Agent 链不校验(继承用户自身权限,无需 consent,零回归)。
- 用户未授权时应用应引导用户授权后重试(应用侧弹确认框 → 调下方授权端点 → 重发 exchange)。
用户自助管理端点(Authorization: Bearer <用户 token>):
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/my/delegation-grants | 我的授权列表(含 client 显示名、granted_at/revoked_at;已撤销的也列出,便于重新授权) |
| POST | /api/my/delegation-grants | 记录授权,body {client_id, audience, scope};幂等——撤销过的重新激活(upsert),已有的刷 granted_at |
| DELETE | /api/my/delegation-grants/:id | 撤销授权,下次换票即 consent_required;重复撤销 404 |
链式委托(Agent 的 Agent)
拿委托 token 再换一次,act 自动按规范嵌套(最外层 = 最新 actor,最内层 = 最初 actor):
{
"act": {
"sub": "agent_worker_b",
"act": { "iss": "...", "sub": "agent_manager", "agent_id": "agent_007" }
}
}
消费方做访问控制只看顶层 claims 与最外层 actor,嵌套链仅作审计线索(RFC 8693 §4.1)。
introspect
委托 token 与服务身份 token 均可在 introspect 响应中拿到 act / agent_id:
POST /oauth/introspect
token=<委托 token>
{
"active": true,
"sub": "42",
"username": "alice",
"client_id": "agent_manager",
"act": { "iss": "...", "sub": "agent_manager", "agent_id": "agent_007" },
"agent_id": "agent_007"
}
用户/委托 token 的 introspect 补查 users.is_active(2026-09-29 起):管理员停用账号后,该用户存量 token(含委托 token)introspect 立即 active: false,不必等 15 分钟自然过期。client token(token_type=client)无用户维度,不受影响。
审计
每次 exchange 落审计事件 TOKEN_EXCHANGED(subject + agent client + agent_id + resource),后台「审计日志」可查。工具级归因配合决策审计(见 Agent 资源授权)构成 人 → Agent → 工具 全链。