Agent 身份与委托授权:Agent 调工具时拿的是谁的身份
比较 Shared Service Credential、User-delegated Token、Short-lived Capability 与 Approval-scoped Grant 四种 Agent 身份拓扑:凭证由谁签发、写的是谁替谁、有多窄多短、人有没有在执行前看过这一笔,以及凭证用错时事故有多大、怎么收住。
Agent 调用工具时,工具 API 只能按它拿到的凭证授权,模型说什么都不算。所以 Agent 身份设计只有一个问题:Agent 去调用工具时,它拿的是谁的身份? 展开是四问:凭证由谁签发、写的是谁替谁、有多窄多短、人有没有在执行前看过这一笔。
想亲手走一遍四种拓扑、注入故障再恢复,打开互动 Lab:/system-design-lab/agent-identity-delegated-authorization-architectures。
有约束的设计问题
一个 Agent 平台要接四类任务:每晚跑一份汇总报表,没有任何用户上下文;替已登录的用户读写他自己的邮件和文件,事后要能归因到人;一个多工具运维 Agent,Agent 自己就是主体,要调十几个内部 API,绝不能存长期密钥,每次调用只拿这一次需要的权限;付款、删除、群发这类不可逆的动作,而且 Agent 读到的内容里可能藏着注入指令。每一类该用哪种身份拓扑?
四种 topology signature
| 架构 | 以谁的身份行动 | 谁签发 | 范围与寿命 | State owner | 适合 | 主要代价 |
|---|---|---|---|---|---|---|
| Shared Service Credential | 服务 | 运维人员,一次性 | 服务能做的一切,长期 | credential store | 没有用户上下文的单一用途后台任务 | 所有用户的动作长一样;一次泄露或注入暴露服务能碰到的一切 |
| User-delegated Token | 用户,Agent 是 actor | authorization server 做 token exchange | 用户同意过的 scope,短期 | authorization server(consent 记录) | 操作用户自己的数据:邮件、文件、工单 | 要有已登录且同意的用户;MFA 会打断;入站 token 绝不能转发 |
| Short-lived Capability | 工作负载,降权后的 | token broker 按 attestation 铸 | 一个 aud、一组动作、几分钟 | broker policy(每个工作负载能铸什么) | 多工具、批处理、定时任务,每次调用最小权限 | broker 是必经之路;aud 铸错或 TTL 太长就变回共享密钥 |
| Approval-scoped Grant | 用户,只对这一笔 | authorization server 在带外批准之后 | 恰好批准的内容,单次 | transaction grant | 付款、删除、群发等不可逆动作 | 要等人;会批麻木;binding message 必须显示真实内容 |
1. Shared Service Credential:一把长期密钥,所有人的动作都长一样
Agent 运行时保存一把长期有效的服务凭证,不管哪个用户交来任务,都用同一个服务身份调用工具 API。API 看到的永远是「那个服务」,凭证能做什么,Agent 就能替任何人做什么。
OWASP 在 LLM06:2025 Excessive Agency 里点名了这个模式:「一个本该在单个用户上下文里操作的 LLM 扩展,用一个通用的高权限身份访问下游系统」;防御是「跟踪用户的授权和安全范围,确保替用户执行的动作在下游系统里以那个用户的上下文执行」。Google 的服务账号最佳实践说得同样直接:「尽可能避免使用服务账号密钥」,每个用途一个服务账号,因为「多个应用共享一个服务账号会让管理复杂化」,替最终用户做事优先用用户凭证,并打开 IAM 的数据访问日志。
代价在于 API 分不清用户。一段藏在网页里的指令让 Agent 去查另一个客户的资料,凭证有权限,API 照常返回,跨用户泄露就发生了,日志里只写着「服务读取」。按用户授权只能在 Agent 内部自己做,而 OWASP 明确说不要依赖模型来判断动作是否被允许。长期密钥从日志里泄露则更糟:拿到它的人就是这个服务,而且它不会过期。只把这个架构留给没有用户上下文的单一用途任务,用不可下载的密钥、最小权限和单一用途账号。
2. User-delegated Token:用用户的 token 换下游 token,Agent 是 actor
用户登录后,Agent 拿到的是发给 Agent 自己的用户 token。要调用工具 API 时,Agent 不把它直接转发,而是去授权服务器换一张新的:subject 还是这个用户,actor 写上 Agent,受众(aud)只有那一个 API。API 校验受众后按用户的权限授权,审计日志记下「谁替谁」。
RFC 8693 把委托和冒充分得很清楚:委托时「A 仍然有独立于 B 的身份,B 把部分权利委托给了 A,但任何动作都是 A 代表 B 做的」;冒充时接收方「实际上就是在和 B 打交道」。act 声明「表达委托已经发生并标识被委托的行动方」,may_act 声明谁被允许成为 actor。Microsoft 的 On-Behalf-Of 流程给了工程细节:中间层「只使用委托 scope,不使用应用角色」,用来换的 token「必须以发起 OBO 请求的应用为 aud」,并且「不要把签发给中间层的 access token 发到预期受众之外的任何地方」。下游要求 MFA 时授权服务器返回 interaction_required,中间层要把这个挑战原样交给客户端去完成,客户端「不应该用缓存的 access token 重试」。MCP 的授权规范把同一条规则用到工具服务器上:MCP 服务器「不得把从客户端收到的 token 透传」给上游 API,客户端「必须实现 Resource Indicators」让每张 token 绑定一个服务器。
代价是必须有已登录且同意过的用户,MFA 会把任务打断,而且 token 只能换、不能转。Agent 为了省一次 exchange 把入站 token 原样放进上游请求头,就是 confused deputy:那张 token 的受众是 Agent,API 如果不校验 aud,任何一张给 Agent 的 token 都能直接打 API。每个上游 API 单独换一张带自己 aud 的 token,API 拒绝受众不是自己的 token,作为最后一道防线。
3. Short-lived Capability:工作负载证明身份,broker 铸一张几分钟的窄 token
Agent 自己没有任何长期密钥。运行时通过 attestation 拿到工作负载身份,凭它向 token broker 申请「我要对这个 API 做这个动作」;broker 按策略检查这个工作负载被允许铸什么,签一张只对那一个 API、只含那几个动作、几分钟就过期的 token。过期就重新 attest,不存 token。
SPIFFE 是这套身份的标准形态:工作负载通过 Workload API 拿到 SVID,不需要预共享密钥,「所有私钥(和对应的证书)都是短期的,频繁且自动轮换」。Google 的做法是「用 Service Account Credentials API 做临时权限提升」,由一个配置为 token broker 的监督服务账号签发短期 token,并「用 Credential Access Boundaries 给 access token 降权」。RFC 8707 给出了 aud 单一的理由:「access token 必须只在特定的受保护资源、特定的访问范围内有效」,「合法地呈现给某个资源的受众受限 token,不能被那个资源拿去别处非法访问其他资源」。
代价是 broker 成了所有调用的必经之路,而策略就是一切。一次策略改动把工单 API 和文件 API 的行写串了,broker 给「读工单」的申请铸出了 aud 是文件 API 的 token,Agent 拿着它去读文件也能成功。修法:每次铸 token 都带 resource indicator,铸出的 aud 必须等于申请的 API,API 只接受受众是自己的 token,给每条策略写测试。另一个是 TTL:有人为了少一次 attest 把 max_ttl 改成七天,每张 token 都能用一星期,短时能力名存实亡。在策略里强制分钟级上限,续用靠重新 attest 而不是保存 token。
4. Approval-scoped Grant:人先批准这一笔,token 只绑这一笔
对付款、删除、群发这类不可逆动作,Agent 不直接执行,而是把这一笔的具体内容(类型、金额、对象)作为提议放进待批准队列。用户在自己的设备上看到同样的内容并批准,批准记录成为一条交易授权;据此签发的 token 只对这一笔有效,API 拿到请求会逐字段和批准的内容比对,不一样就拒绝。
RFC 9396 Rich Authorization Requests 解释了为什么 scope 不够:它「不足以表达细粒度的授权需求,例如『请允许我向 Merchant A 转 45 欧元』」;authorization_details 带着 type、actions、locations 和 instructedAmount、creditorName 这样的交易字段,用户「也可以只批准请求的一个子集」,授权服务器「签发的 token 与相应的 authorization_details 关联」。CIBA 提供带外批准:客户端在后台通道发起,「用户在认证设备上认证并授予同意」,binding_message 是「一段人类可读的内容,同时显示在消费设备和认证设备上」;只有当用户「已经认证并授权了请求」,授权服务器才通过 poll、ping 或 push 返回 token。OWASP 的对应建议是「高影响的动作在执行前要求人工批准」,并且「在下游系统里实现授权,而不是依赖 LLM 判断动作是否被允许」。
代价是要等人,批得多了人会麻木,而 binding message 必须显示真实内容。用户批准了 123.50 EUR 给 A,被注入的 Agent 却提交 500 EUR:API 逐字段比对 grant,不一致就拒绝并记录,grant 单次有效、短期过期,Agent 不能自己改回批准的数值再试,要把拒绝如实告诉用户。推送模板如果只显示「Agent 请求执行一项操作,是否确认?」,用户批准的是他没看到的内容,带外批准就退化成一个「是」按钮;binding message 要从同一条 authorization_details 生成,显示金额、对象和动作,并且只把高风险动作送去批准。
四种拓扑共同的底线
- 授权在下游 API,不在 Agent,更不在模型。 Agent 的职责是呈现一张最窄的、如实写着「谁替谁」的凭证。
- 每张 token 一个受众。 每个上游 API 单独换或单独铸,API 校验
aud,入站 token 永远不转发。 - 优先 attestation,其次才是存密钥。 必须有密钥时轮换它,并对来自意外来源的使用报警。
- step-up 和批准不能在 Agent 内部绕过。 拿不到新凭证就暂停任务,拿到后从断点继续,不用缓存的 token 重试。
- 日志要回答「谁替谁」。 每次工具调用记下 subject、actor、aud、scope 或 details、过期时间;有批准的还要记谁在何时批了什么。
故障与恢复
| 架构 | 故障 | 用户看到什么 | 恢复 |
|---|---|---|---|
| Shared Service Credential | 被注入的任务用服务身份读了别人的记录 | 别人的数据出现在自己的回答里 | 替人做事改用委托 token;服务账号单一用途、最小权限;授权放在下游 |
| Shared Service Credential | 长期密钥从日志里泄露 | 无感知,直到发现异常使用 | 不签发可下载密钥;工作负载身份 + 短时 token;轮换并报警 |
| User-delegated Token | Agent 把入站用户 token 原样转发给上游 API | 无感知;任何一张给 Agent 的 token 都能打 API | 每个 API 单独 exchange 一张带自己 aud 的 token;API 校验受众 |
| User-delegated Token | 下游要求 MFA,换 token 返回 interaction_required | 任务停在半路 | 把挑战交给用户完成,暂停后从断点继续;不用缓存 token 重试 |
| Short-lived Capability | broker 铸出了 aud 写错的 token | 无感知;工作负载越过了允许的边界 | 每次铸带 resource indicator;API 只接受 aud 是自己的;策略逐条测试 |
| Short-lived Capability | 策略把最长 TTL 定成几天 | 无感知;泄露一张就有一周窗口 | 策略强制分钟级 TTL 上限;续用靠重新 attest |
| Approval-scoped Grant | Agent 提交的金额和批准的不一样 | 若 API 不比对就多付了钱 | token 绑定 authorization_details;API 逐字段比对并拒绝;grant 单次 |
| Approval-scoped Grant | binding message 只写「确认操作」 | 用户盲批 | 从同一条 details 生成显示内容;只批高风险动作;显示差异不是按钮 |
| Approval-scoped Grant | 批准疲劳 | 用户闭眼点通过 | 低风险动作走委托或短时 token;批准界面显示具体内容 |
怎么选
- 没有用户上下文的单一用途后台任务:Shared Service Credential,单一用途账号、不可下载密钥、最小权限、数据访问日志。
- 替用户操作他自己的数据:User-delegated Token,exchange 而不是转发,保留 actor,每张 token 一个 aud,step-up 交给用户。
- 多工具或定时运行、Agent 自己是主体:Short-lived Capability,attest 后由 broker 按策略铸 aud 单一、动作单一、分钟级的 token。
- 不可逆或高影响动作:Approval-scoped Grant,提议具体交易,binding message 显示真实内容,token 绑定 details,单次短期。
- 无论哪种,下游 API 负责授权,日志说清谁替谁。
回到开头的四类任务:每晚报表走 Shared Service Credential;替用户读邮件走 User-delegated Token;多工具运维走 Short-lived Capability;付款和删除走 Approval-scoped Grant。
面试时这样回答
- 先复述约束:有没有用户上下文、动作可不可逆、Agent 是不是自己作为主体、能不能存密钥。
- 说凭证路径:点名图上的边,例如「读服务密钥后以服务身份调用」「用用户 token 换 subject 是用户、actor 是 Agent 的下游 token」「attest 后 broker 按策略铸窄 token」「提议、带外批准、绑定 details 执行」。
- 说身份状态存在哪:凭证仓库、授权服务器、broker 策略,还是交易授权记录。
- 说代价与一个故障:例如入站 token 被转发就是 confused deputy,修法是每个 API 单独 exchange 并校验受众。
一手证据
- IETF:RFC 8693 OAuth 2.0 Token Exchange
- Microsoft Learn:Microsoft identity platform and OAuth2.0 On-Behalf-Of flow
- IETF:RFC 9396 OAuth 2.0 Rich Authorization Requests
- SPIFFE:SPIFFE Concepts
- Google Cloud:Best practices for using service accounts
- Model Context Protocol:Authorization(2025-06-18)
- OpenID Foundation:Client-Initiated Backchannel Authentication Core 1.0
- OWASP:LLM06:2025 Excessive Agency
- IETF:RFC 8707 Resource Indicators for OAuth 2.0