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 是 actorauthorization 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 带着 typeactionslocationsinstructedAmountcreditorName 这样的交易字段,用户「也可以只批准请求的一个子集」,授权服务器「签发的 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 TokenAgent 把入站用户 token 原样转发给上游 API无感知;任何一张给 Agent 的 token 都能打 API每个 API 单独 exchange 一张带自己 aud 的 token;API 校验受众
User-delegated Token下游要求 MFA,换 token 返回 interaction_required任务停在半路把挑战交给用户完成,暂停后从断点继续;不用缓存 token 重试
Short-lived Capabilitybroker 铸出了 aud 写错的 token无感知;工作负载越过了允许的边界每次铸带 resource indicator;API 只接受 aud 是自己的;策略逐条测试
Short-lived Capability策略把最长 TTL 定成几天无感知;泄露一张就有一周窗口策略强制分钟级 TTL 上限;续用靠重新 attest
Approval-scoped GrantAgent 提交的金额和批准的不一样若 API 不比对就多付了钱token 绑定 authorization_details;API 逐字段比对并拒绝;grant 单次
Approval-scoped Grantbinding 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。

面试时这样回答

  1. 先复述约束:有没有用户上下文、动作可不可逆、Agent 是不是自己作为主体、能不能存密钥。
  2. 说凭证路径:点名图上的边,例如「读服务密钥后以服务身份调用」「用用户 token 换 subject 是用户、actor 是 Agent 的下游 token」「attest 后 broker 按策略铸窄 token」「提议、带外批准、绑定 details 执行」。
  3. 说身份状态存在哪:凭证仓库、授权服务器、broker 策略,还是交易授权记录。
  4. 说代价与一个故障:例如入站 token 被转发就是 confused deputy,修法是每个 API 单独 exchange 并校验受众。

一手证据