作者指出AI Agent场景下API密钥作为唯一认证的三个缺陷:无法回答"你是谁"、"能做什么"、"谁授权及有效期"三个问题,倡导为Agent引入独立身份层和作用域授权。
一个 secret(密钥)回答的问题是:你知道这个字符串吗?而 identity(身份)回答的是一套完全不同的问题。你到底是谁?你被允许做什么?谁授予了你这个权限,有效期是多久?当 API key 成为你的整个安全模型时,你回答了第一个问题,而悄悄跳过了其他三个。
在调用方是你自己团队编写和部署的可预测后端服务时,这种做法还可以忍受。但当调用方是一个 AI agent——它在做自主决策、链式调用工具、有时代表它从未直接认证过的人类用户行动时,这就无法接受了。这个 agent 在旧意义上不再是"你的服务"了。它更接近于一个半独立的行动者,需要自己可验证的身份、自己作用域受限的权限、以及自己的审计日志。
让我列出我看到的裂痕,而不是假装这是什么整齐的通用法则:
无法委托。 API key 无法说"我代表用户 X 行事,已获得 X 的同意,有效期 15 分钟"。它是一种扁平的、全有或全无的凭证。
没有任何意义的过期机制。 大多数 API key 永久有效,直到有人想起来轮换它们——这基本上意味着永远不会。
无法绑定 audience 或 scope。 读取客户记录的同一把 key 通常也可以删除它。没有"此令牌仅对这一个资源服务器、这一个操作有效"的内置概念。
审计体验极差。 当三个不同的 AI agent 共享同一把 key——实践中经常如此——你的日志无法告诉你到底是哪个 agent 做了什么。你在日志行中得到的是一个名字,而不是证明。
撤销是一把钝刀。 你无法在不破坏共享该 key 的其他所有东西的情况下,撤销"这一个 agent 对这一个工具的访问权限"。
这些其实都不是新问题了。企业软件在十五年前就经历过完全相同的争论,当时所有人从共享静态凭证转向了基于 OAuth 的委托访问。现在不同的是,另一端的调用者不再是人类在 consent 界面上点击"允许",而是一个自主 agent,整个流程需要在没有人类坐在那里批准每一步的情况下完成。

在上面的 API key 模型中,没有什么可以推敲的。这把 key 要么能用,要么不能用,一旦泄露,它后面的一切都泄露了。现在来对比一下,一个 proper 的 agent 身份流程是什么样的。

这里重要的转变是:agent 不再持有一把永久性的、授予一切权限的 secret。它持有的是一枚 token,而这枚 token 是关于身份、作用域和时间的声明。资源服务器信任这枚 token,不是因为它难以猜测,而是因为它是由一个已信任的身份提供商签发的,而且它可以在执行任何操作之前检查该 token 到底被允许做什么。
这就是 OAuth 2.0 client credentials grant,或者更好的方案——OAuth 2.0 Token Exchange(RFC 8693)变得相关的地方,适用于 agent-to-agent 和 agent-to-tool 调用。Token Exchange 尤其为这种情况而生:一个持有某 token 的 agent 需要调用下游服务,并获得一个更窄、更具体的 token,可能还代表着原始用户行事。这是 proper 的委托,而不是一个共享 secret 冒充委托。
这是 API key 版本的大致样子。简单,也是整个问题所在:
# 大多数 agent 今天交付的"身份策略"
import requests
response = requests.get(
"https://api.internal-service.com/v1/customer-records",
headers={"X-API-Key": "sk_live_9f8a7...never_rotated"}
)
# 没有 subject,没有 scope,没有 expiry,无法判断是哪个 agent 调用了它。
现在对比一下通过 client credentials 获取、作为 bearer token 传递的 token——token 本身携带了 claims:
import requests
token_response = requests.post(
"https://idp.example.com/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": "agent-invoice-reader",
"client_secret": AGENT_SECRET, # 短生命期、轮换、仅限这一个 agent 的 scope
"scope": "invoices:read",
"audience": "https://api.internal-service.com",
},
)
access_token = token_response.json()["access_token"]
response = requests.get(
"https://api.internal-service.com/v1/customer-records",
headers={"Authorization": f"Bearer {access_token}"},
)
# token 携带了 sub、scope、aud 和 exp claims。
# 资源服务器在执行任何操作之前可以验证全部四个。
第二个版本有更多移动部件,这一点我不否认。但这些移动部件正是让你能够回答"哪个 agent 做了这个、它是否被允许、以及有效期多久"而无需猜测的原因。如果你解码那个 access token(它通常是一个 JWT,RFC 7519),你会得到一个 sub claim 标识该 client、一个 scope claim 限制它可以做什么、一个 aud claim 将其绑定到一个资源服务器、以及一个 exp claim 使整个东西自动过期。API key 给你的是这些保证中的零。
如果你是认真地在设计这个,有三份文档值得你花时间而不只是浏览:
OpenID Connect 也值得了解,尽管它解决的是一个略有不同的问题:人类最终用户的身份认证。大多数 agent-to-service 调用更接近于纯 OAuth client credentials 或 token exchange 的领域。
API key 只能告诉你调用者知道一个字符串。它无法告诉你调用者是谁、它被允许做什么、以及该权限何时到期。AI agent 现在的自主性已经足以让这个差距不再是理论上的了——它正在作为真实的事故显现。如果你的 agent 整个安全模型就是这一个静态 header 值,你没有身份策略——你只是有一个多了几步的共享密码。
把你的 agent 当作它们真正的半独立行动者来对待:给它们真正的、作用域受限的、有过期时间的身份,而不是三个月前部署时复制到配置文件里的那把 key。