别把 API Key 当身份:Agent 开始替你下单、删文件、发邮件,身份与授权的四件套该补上了

传统 OAuth 的 calendar.read 是为人类逐次点击设计的,无人值守的 Agent 需要的是委托链、token 绑定和外置化授权。本文拆解 RFC 8693 委托、RFC 8707 资源指示、AuthZEN PEP/PDP、A2A 签名 Agent Card 与 SPIFFE 工作负载身份,附可落地配置与踩坑清单。

工程实践 已修订 AI Agent 身份与授权 OAuth 2.0 MCP 安全 A2A 零信任 SPIFFE AuthZEN

引言

7 月 28 日 MCP 史上最大规模的修订落地——无状态化 + OAuth 2.1 强制,协议层这半年终于收敛了。但对大多数团队来说,这轮修订里藏着一个被忽略的信号:工具调用层怎么连已经定案,可”谁在替 Agent 背锅”这个问题,答案还是空白。

先做一个直观对比。你的日历应用拿到 calendar.read 权限,是因为你亲手点了一次”允许”,之后它每次调 API,服务器都默认”是你本人在操作”。但一个无人值守的 Agent 完全不是这么回事:它可能连续几个小时、成百上千次地调工具,中间没有任何人点”允许”。你授权的是”这个 app”,而真正动手的是”这个 app 派出去的 Agent 子任务”。

这一层差别,正是传统 IAM 失效的地方。

传统 IAM 回答的是”这个人在这个应用里能做什么”;Agent IAM 要回答的是”这个自主实体,代表谁、被允许做什么、出事时追到谁”。 前者两问,后者三问,多出来的那一问——“代表谁”——就是 Agent 时代最贵的工程债。

一、委托链:Agent 不代表自己,它代表”你”

核心原语是 OAuth 2.0 Token Exchange(RFC 8693)里的 act(acting party)和 may_act 两个 claim。

场景:用户授权主应用访问邮箱,主应用又派一个 Agent 去”整理收件箱”。如果 Agent 直接拿主应用的 token 去调邮箱 API,服务器看到的是”主应用”在操作,而不是”Agent 代表用户”——出了事,审计日志上分不清是用户、主应用还是 Agent 干的。

正确做法是让主应用用 Token Exchange 把 token 换一张”受托 token”,把委托关系写进 claim:

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<主应用的 access_token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=<Agent 的身份 token>
&actor_token_type=urn:ietf:params:oauth:token-type:jwt
&scope=email.read
&audience=mail.example.com

返回的 token 里会有这样一段:

{
  "sub": "user-42",            // 最终受益人:用户
  "act": { "sub": "agent-7" }, // 实际执行者:Agent
  "scope": "email.read"
}

act claim 记录了完整的委托链:用户 → 主应用 → Agent。这正是 OpenID Foundation《Identity for Agentic AI》白皮书(2025-10)强调的”on-behalf-of 委托,而不是 impersonation(冒充)“——Agent 永远不能冒充用户,只能以”受托者”身份行动,链上的每一环都可审计。

踩坑 1:很多团队图省事,让 Agent 直接用用户的主 token 或一把万能 API key。这等于把整条审计链压平——Agent 干坏事时,日志显示”是用户干的”。委托链必须显式记录,省不得。

二、Token 绑定:杀掉”token 挪用”

第二个坑是 token passthrough(token 挪用)。假设 Agent 同时接了两个 MCP server:一个正经的文件服务器,一个恶意的”翻译”服务器。Agent 把带权限的文件 token 发给翻译服务器,翻译服务器转头就能拿这把 token 去访问文件服务器——因为 token 本身没有限定”只能用在哪”。

MCP 规范早在 2025-06 就把 RFC 8707(Resource Indicators,资源指示符)设为 HTTP 传输的强制项,为的就是解决这个:

POST /token
grant_type=authorization_code
&code=...
&resource=https://files.mcp.example.com   // 把这把 token 锁死到这一个资源

加了 resource 参数后,签发的 token 会在 aud(audience)claim 里绑定目标资源服务器。文件服务器之外的任何地方拿这把 token 去验,都会被拒。2026-07-28 的新规范进一步把授权对齐到 OAuth 2.0 / OpenID Connect,并要求所有 server 支持 OAuth 2.1——这意味着所有基于旧 session 状态写的 MCP 客户端,auth 流程基本都要重写一遍。

踩坑 2:迁移到新规范时,别只改 transport(Streamable HTTP),auth 层没跟上等于白改。老代码里 Mcp-Session-Id 那套粘性会话逻辑删掉后,token 的 audience 校验要重新铺到每个 server 上,漏一个就是一把裸奔的 token。

三、授权外置化:把”能不能做”从 Agent 代码里拿出来

粗粒度 OAuth scope(calendar.read)是为”人类点一下用一次”设计的,它回答不了 Agent 场景的关键问题:“Agent 要在无人值守下连续调用几百次,每一次动作该不该放行?”

答案是把授权从 Agent 的代码里外置出来,落到一个独立的策略决策点(PDP)。OpenID Foundation 的 AuthZEN 工作组正在标准化的,就是这个 PEP(策略执行点)↔ PDP(策略决策点)之间的接口——和零信任网络里的那套拆分一模一样:

Agent 想调 delete_file(file=report.pdf)
        │
        ▼
PEP(挡在工具前面的门)── "这个动作允许吗?" ──▶ PDP(策略引擎)
        ▲                                              │
        │              allow / deny                     │
        └──────────────────────────────────────────────┘

PEP 在动作执行之前把请求上下文(谁、什么动作、什么资源、什么条件)发给 PDP,PDP 返回 allow/deny。这带来两个关键性质:

  1. 策略在 Agent 代码之外——Agent 被 prompt 注入劫持后,它改不了策略,因为它根本没有策略的写权限。
  2. 确定性前置检查——对不可逆动作(转账、删除、发送),在它执行前拦下来,而不是事后靠行为检测补救。

一个最小化的 PEP 实现,概念上是这样的:

def guarded_call(tool_name, args, agent_ctx):
    decision = pdp.evaluate(
        subject=agent_ctx.agent_id,
        resource=f"{tool_name}:{args.get('path')}",
        action="invoke",
        context={"risk": "irreversible" if tool_name == "delete_file" else "reversible"},
    )
    if decision != "allow":
        return PermissionDenied(reason="policy denied pre-action")
    return tools[tool_name](**args)

踩坑 3:PDP 的决策必须快。授权在每次工具调用前都发生,一个几百毫秒的授权服务会让 Agent 循环慢到不可用。生产里通常用本地缓存 + 短期授权令牌把 PEP 的检查做成进程内、微秒级,而不是每次工具调用都打一次网络。

四、Agent 间身份:A2A 的签名 Agent Card 与 SPIFFE

多 Agent 场景还有最后一个身份问题:Agent A 调 Agent B,B 怎么知道对面是真 A,还是被劫持的冒牌货?

A2A 协议(v1.0,2026-03-12 发布,Linux Foundation 治理,150+ 组织支持)用 Agent Card 解决发现,用 Signed Agent Card 解决身份。Agent Card 是放在 /.well-known/agent-card.json 的 JSON 文档,声明能力、鉴权方式、授权要求;v1.0 引入了 JWS(JSON Web Signature)签名版,让调用方在第一次请求前就能密码学地验证”我到底在和谁说话”:

{
  "name": "expense-approver",
  "url": "https://agents.example.com/expense",
  "capabilities": { "approve_expense": true },
  "securitySchemes": { "auth": "oauth2" }
}

而工作负载身份这块,SPIFFE/SPIRE 已经相当成熟:它给每个 Agent 发放一个短生命周期的加密身份(SVID),而不是一把长期有效的 API key。API key 一旦泄露就长期有效、难以吊销;SVID 默认几十分钟过期、自动轮换,泄露窗口被压缩到最小。

v1.0 的签名机制值得展开说一层。 给一个 JSON 文档签名听起来简单,但 JSON 的序列化是不确定的——键顺序、空白、Unicode 转义方式都会改变字节序列,从而改变签名值。如果不先做规范化,同一个 Agent Card 在不同实现里签出来的结果对不上,验证方就无法复现签名。A2A v1.0 因此把签名定义为 JWS + JSON 规范化(依 RFC 8785 做 JSON Canonicalization),规范第 8.4 节专门规定了规范化要求、签名格式与验证流程:

签名流程(概念)
  AgentCard JSON ──▶ RFC 8785 规范化(键排序 / 数字格式 / Unicode 归一)
                 ──▶ JWS 签名(依 RFC 7515)
                 ──▶ AgentCardSignature 对象
  验证方:取原始 JSON ──▶ 同样的规范化 ──▶ 验签

这一层是 v1.0 才补齐的。如果你在 v0.3 时代做过 Agent Card 签名,升级时要重做——签名格式的变更是破坏性的,两边的规范化方式不同就验不过。

v1.0 在安全上还做了几处收紧:新增设备码流(Device Code,RFC 8628),移除已废弃的 implicit 与 password 流,为授权码流增加了 pkce_required 字段,并把双向 TLS(mTLS)列入安全方案声明。这几项加起来意味着 v1.0 的默认安全基线明显高于 0.3——但也意味着从 0.3 升级时,鉴权部分几乎必然要改。

踩坑 4:A2A 的协议只”声明”了六种鉴权方式(APIKey / Basic / Bearer / OAuth 2.0 / OpenID Connect / mTLS),但它不强制任何一种——加固是 implementer 自己的活。别以为”接上 A2A 就安全了”。

踩坑 4b(v1.0 专属):v1.0 引入了 GetExtendedAgentCard,把”未认证的基础卡”与”认证后的扩展卡”分成两次获取。如果沿用老逻辑只取基础卡,你会拿到一份被裁剪过的能力声明——基于不完整的 Agent Card 做路由决策,可能导致某些技能永远不被调用。实现时要显式判断是否支持扩展卡,并走带外 OAuth 流程获取。(依据 A2A v1.0 规范第 8 章「代理发现:代理卡」)

五、落地清单:给 Agent 补上身份的四条铁律

把上面的东西收敛成四条可以直接执行的规则:

  1. 最小上下文 + 最小权限:Agent 能看多少上下文、能碰哪些工具,逐任务、限时授权,而不是一次性给足。
  2. token 按资源隔离:一个仓库一把 PAT、一个频道一把 Slack token,不用 workspace 级万能 key;每把 token 都绑 audience。
  3. 不可逆动作必须人审:转账、删除、外发这类动作,集中到一个很小的清单里做 human-in-the-loop,其余可逆动作才自动放行。
  4. 可逆性是最强的授权后盾:一个能撤销的动作,比一个不能撤销的动作需要的上游门槛低得多。设计工具时优先保证可回滚。

结论

MCP 协议层已经收敛(无状态化 + OAuth 2.1 强制),A2A 也走到了 v1.0。但”谁在替 Agent 背锅”这件事,标准只能给到一半——委托链、token 绑定、授权外置化、工作负载身份,这四件套最终还是要落在每个团队的工程里。

当 Agent 从”帮你查个东西”进化到”替你下单、删文件、发邮件”,身份就不再是一个可以后补的 feature,而是它能不能被放进生产系统的入场券。下一阶段的竞争,不是谁家的 Agent 更能干,而是谁家的 Agent 出了事能追责、能兜底。 这一步补不上的团队,Agent 越能干,风险越大。

关联内容

文章 ⬆️ 前置
MCP 协议的设计性 RCE:当 150M 下载量的基础设施本身就是漏洞
OX Security 披露 MCP 架构级远程代码执行漏洞,200K+ 服务器暴露,Anthropic 承认 'by design'——AI Agent 生态的供应链安全问题已经不能再拖了。
文章 ⬆️ 前置
MCP Server 生产级部署实战:从 hello world 到安全沙箱
基于 MCP 协议的开发指南,涵盖 Server 架构设计、Docker 容器化、权限控制与进程隔离的完整实战流程。
文章 🔗 相关
MCP 无状态化今日正式落地:Agent 基础设施的「成人礼」与 7 月最后一周的三个结构性变化
MCP 2026-07-28 规范今日正式发布,移除会话握手与粘性路由,支撑 Agent 工具调用层水平扩展;同一周内 Kimi K3 开源权重、Claude Opus 5 发布,三重信号交汇指向同一结论:Agent 基础设施层正在固化。
文章 🔗 相关
规则写在纸上,Agent 写在心里:HANDBOOK.md 基准 36.2% 通过率撞上 GitHub/Google 治理工具同日发布——本周的 Agent 安全课
本周,GitHub 和 Google 同日发布 Agent 治理功能,而 HANDBOOK.md 基准揭示最好模型也只有 36.2% 遵守书面政策;加上 OpenAI 沙盒逃逸事件后 JFrog 零日链和 Modal Labs 二次泄露的新细节,Agent 安全治理面临根本性拷问。
文章 🔗 相关
当 Agent 开始买单:3 亿笔智能体支付,宣告 AI 经济主体元年
支付宝发布 AI 钱包与 Token Pay,3 亿笔 Agent 支付落地——AI 不再只是工具,而是拥有自主支付能力的经济主体