AgentCore Identity:让 Agent 以「用户身份」访问下游资源
本文以一个开源的飞书(Lark)身份参考实现为例,梳理 Amazon Bedrock AgentCore Identity 的功能、原理与价值,并结合 2025–2026 业界共识,讨论 AI Agent 访问下游资源时「用谁的身份」这一核心问题。面向对 AI Agent 鉴权、企业身份治理感兴趣的工程与安全读者。
一个绕不开的问题:Agent 该用「谁」的身份去访问数据?
当你给一个 AI Agent 接上企业系统——飞书文档、Google Drive、GitHub、内部数据库——很快会撞到一个身份问题:
Agent 去读一份文档时,它是「谁」?
最省事的做法,是给 Agent 配一把「万能钥匙」:一个高权限的服务账号 / API Key,能读所有人的所有数据。Agent 拿着它,谁来问都能答。但这在企业场景里几乎不可接受:
- 越权:用户 A 通过 Agent,能看到本该只有用户 B 能看的数据——因为 Agent 用的是万能钥匙,不区分谁在问。
- 审计失真:下游系统只看到「Agent 服务账号」在访问,无法归因到真实用户。
- 权限漂移:用户在源系统里的权限变了(离职、调岗、文档收回),Agent 那把万能钥匙毫不知情,照样能读。
正确的做法是:Agent 访问下游时,应该以「发起请求的那个真实用户本人」的身份去访问,能看到的恰好就是这个用户本来能看到的,一分不多。这样一来,「谁能访问什么」的裁决,自然落回到下游系统本来就有的权限体系上——Agent 不需要、也不应该自己重新发明一套授权模型。
这正是 Amazon Bedrock AgentCore Identity 要解决的问题。
先厘清三个概念:Identity、Token、Role
在讲 AgentCore Identity 之前,得先把鉴权里最容易混淆的三个词分清楚。它们回答三个不同的问题:
| 概念 | 回答的问题 | 本质 | 提供的能力 |
|---|---|---|---|
| Identity(身份) | 你是谁? | 一个稳定、唯一的主体标识 | 识别与归属的锚点。本身不携带权限,只是个「名字」。 |
| Token(令牌) | 你现在能证明你是谁 / 代表你做什么? | 一段有时效、可验证的凭证 | 承载已认证的身份 + 携带 scope。让下游无需重新登录就能信任你,可限时、可撤销、可限定范围。 |
| Role / Permission(角色/权限) | 你被允许做什么? | 一组权限的命名集合 | 授权判定。决定某个身份能访问哪些资源、执行哪些操作。 |
三者串起来是一条链:
Identity(你是谁,长期稳定)
│ 认证后签发
▼
Token(证明身份 + 携带 scope,有时效)
│ 下游拿 token 里的身份去判定
▼
Role / Permission(这个身份被允许做什么)
一个直觉类比:Identity 是「你这个人」(永久),Token 是「你今天的门禁卡」(会过期、可挂失、只开某几扇门),Role 是「你这张卡背后被授予的权限」。 同一个人可以有多张不同权限的卡。
关键区别在于:认证(你是谁)和授权(你能做什么)是两件事。 AgentCore Identity 的定位,是把「以用户身份获得并管理凭证」这件事做扎实,而把「这个身份能做什么」的裁决,交还给下游资源系统。
AgentCore Identity 做的事:托管「每个用户各自的下游凭证」
很多人第一反应是把 AgentCore Identity 理解成「身份透传」——把用户是谁传给下游。这只说对了一半,而且不是最值钱的一半。
它真正的核心能力,是一个按用户维度托管下游凭证的 Token Vault(令牌保管库):
- 按 userId 键控:每个用户各自的下游访问令牌(如某用户的 Google / GitHub / 飞书 access token),在 Vault 里以「用户身份」为键分别存储,互不串味。
- 自动获取(3LO / OBO):首次访问某下游系统时,通过 OAuth 三方授权(3LO,用户点击同意)或令牌交换(OBO)拿到该用户的令牌,存入 Vault。
- 自动刷新:令牌过期前用 refresh token 自动续期,业务代码无需关心。
- 按调用者注入:每次 Agent 要调下游,Vault 取出正确的那个用户的令牌用上。用户 A 的请求用 A 的令牌,用户 B 的请求用 B 的令牌。
也就是说,它不光传「你是谁」,更负责替你保管好「以你的身份访问下游的那把钥匙」,并在每次调用时精确地取出对应那个用户的钥匙。这是它区别于「自己手写一个 token store + 刷新逻辑」的核心价值——把一套容易写错、且涉及敏感凭证的基础设施,变成托管服务。
授权判定发生在哪里:下游资源系统,不是 Agent
有了「每个用户各自的下游令牌」之后,授权模型就变得非常干净:
真实用户(如 user@corp)
│ AgentCore Identity 从 Vault 取出「这个用户的」下游令牌
▼
Agent 用该令牌调用下游资源系统
▼
下游系统按「令牌代表的这个用户」在本系统里的真实权限裁决
→ 返回的,恰好是这个用户本来能访问的,一分不多
「这个身份能做什么」的裁决,完全落在下游资源系统。 Agent 侧不定义角色、不维护权限表、不持有共享凭证。这带来几个直接好处:
- 零越权:Agent 能拿到的,受限于用户令牌的 scope 和用户在下游的真实权限。用户看不到的,Agent 也拿不到。
- 审计真实:下游系统看到的是真实用户在访问,而非一个模糊的服务账号。
- 权限自动同步:用户在下游的权限变化(收回文档、调岗、离职),下次访问立刻生效——因为用的就是用户本人的令牌,没有独立的权限副本会漂移。
这里有一个常被说错的措辞值得澄清:判定权限的不一定是 IdP(身份提供方)。IdP 负责「你是谁」(认证、发身份令牌);而「这个身份有什么权限」往往由资源系统裁决。两者可能是同一个系统(如飞书既是登录方又是文档系统),也可能是不同系统(如身份来自 Okta,权限由 Salesforce 按该身份裁决)。准确的说法是:由下游资源系统,按透传过来的用户身份,裁决其权限。
更进一步:Agent 该用「自己的身份」还是「用户的身份」?——一个被业界重新定义的问题
前面讲的都是「以用户身份访问」。但一个自然的追问是:Agent 难道不能用自己的身份(一个服务账号 / 机器身份)去访问下游吗?什么时候用哪种?
这个问题在 2025-2026 年的身份治理讨论里被反复辨析,结论出人意料:它是个伪二选一。 我们先看两种模式各自的样子。
两种身份模式
- 服务身份(service / machine identity):Agent 用
client_credentials、静态服务账号或 IAM Role 认证成「它自己」,以一套部署时定死的权限行事,与是哪个用户在用它无关。 - 用户身份 / 代理(on-behalf-of, OBO / delegated):Agent 先认证用户一次,之后在每一跳用 OAuth 令牌交换(RFC 8693 / 7523)把用户令牌换成一个受众收窄、scope 更小的新令牌——用户身份(
sub)随调用一路透传,而 Agent 以一个独立的act/actor 声明出现。
各自的优劣(业界有共识)
| 维度 | 服务身份 | 用户身份 / OBO |
|---|---|---|
| 适用数据 | 系统/组织所有(日志、共享知识库、聚合分析) | 用户个人数据(邮箱、文档、私有记录) |
| 用户在场 | 否(后台、定时、批处理) | 是(交互式、实时) |
| 审计归因 | 归到「系统」 | 归到具体某个人,Agent 作为 actor |
| 撤权语义 | 与任何用户的权限状态无关 | 继承用户令牌交换时的实时权限状态 |
服务身份——优点是简单、无需同意流、适合后台与跨用户聚合;缺点是它正是「混淆代理人(confused deputy)」和「环境权限(ambient authority)」的温床:Agent 的权限是部署时定死的静态天花板,与任何用户的实时权限脱钩。用户离职、降权、权限被收回后,服务账号照跑不误——这是治理漏洞,不是 bug。几乎所有厂商(AWS、Microsoft、Okta、WorkOS)和安全研究界(CSA、SANS)都把它列为头号反模式。
用户身份 / OBO——优点是实时保留每用户授权状态(撤权在下次令牌交换即生效)、下游可独立校验、审计精确;且按 RFC 8693,令牌交换只能收窄权限、绝不放大——正如 Christian Posta 所述:「令牌交换必须被理解为权限的削减,而非放大」("Explaining OAuth Delegation, 'On Behalf Of', and Agent Identity for AI Agents", 2025-12)。缺点是两端都需要合规的授权服务器、每跳有额外延迟与复杂度;而且——关键——仅靠 OBO 并不足够安全:如果一个高权限用户(比如拥有全生产环境权限的工程师)来调用 Agent,纯 OBO 会把 Agent 的权限抬到这个用户的天花板,而这可能远超 Agent 当前任务真正需要的范围。这被称为「天花板问题」。
补充:服务身份场景下,AgentCore Identity 依然有价值。 上面主要在批评「裸服务账号 + 静态大权限」这个反模式,但这不等于服务身份本身不该用——后台任务、跨用户聚合等场景它正合适。此时 AgentCore Identity 的价值只是从「按用户键控的 3LO 令牌」切换为:给 Agent 一个独立的一等身份(Workload Identity)(满足共识第 3 条,而非隐身在共享服务账号后),以及托管应用级凭证(2LO / client_credentials、API Key)——同样是自动获取、自动刷新、不把裸密钥暴露给推理层。也就是说,「令牌保管库隔离裸密钥」这条价值与「是不是每用户」无关,两种模式都成立。(这几点属于产品能力层面,该参考实现本身只走了每用户 3LO 路径、未实测服务身份路径;计划另出一篇专讲「Agent 以自身身份行事」的方案与实践,此处不展开。)
共识答案:交集规则(不是「二选一」,也不是「只用 OBO」)
这里是所有严肃来源(AWS、Microsoft、Okta/Auth0、WorkOS)独立收敛到的同一个答案,也是「服务身份 vs 用户身份」被明确判定为伪命题的地方:
Agent 需要它自己独立的一等身份,用户身份也要透传下去;而每一次工具调用的有效权限 = Agent 自身配置的 scope ∩ 用户当前的实时权限——取交集,在调用时计算,不是部署时定死;既不是两者的并集,也不是任何一方单独说了算。
- WorkOS 把它明确称为「交集规则」:Agent 在任一工具调用时的有效权限,必须是「Agent 自身配置权限」与「请求用户当前权限」两个集合的严格交集——是重叠,不是并集(Delegated access for AI agents: The intersection rule)。
- AWS 的 AgentCore OBO 机制里,交换后的令牌同时携带两个身份:下游按
sub(为谁)做授权判定,把 actor 声明当作审计元数据。AWS 的机器学习博客说得很直白:「以 Agent 的服务身份运行会让审计线索坍缩,因为每个下游系统都必须无条件信任这个 Agent;而原封不动转发用户令牌,则把每个下游工具都变成混淆代理人。」(Implement on-behalf-of token exchange for multi-tenant agents with Amazon Bedrock AgentCore Gateway) - Microsoft Entra Agent ID 把 Agent 形式化为一等主体(既不是服务主体,也不是用户账号),正是为了让这个交集可被计算、可被审计;并明确建议不要用服务主体或共享用户账号来充当 Agent。
- Okta 的表述功能上等价:「这个 Agent,代表这个用户,此刻能不能对这个资源做这个操作?」——运行时用细粒度授权计算,而非静态角色检查。
一个必须点破的推论:最小权限对 Agent 而言不能只活在「凭证发放」这一层(人类那样按天/按岗分配即可),因为 Agent 的任务相关 scope 是逐次调用在变、而非逐日在变的。它必须在每次工具调用的当下评估——用「用户实时权限」和「Agent 自身声明的角色」两者取交集。部署时定死的静态角色/scope,几乎注定要么太窄(Agent 干不了活)、要么太宽(制造上面的环境权限风险)。
威胁模型:混淆代理人(confused deputy)
上面反复出现的 confused deputy,是 AWS、SANS、云安全联盟(CSA)和独立研究者共同引用的统一威胁模型:Agent 是一个持有真实凭证的合法代理人,而提示注入(藏在文档、邮件或工具输出里的恶意指令)诱骗它滥用这份权限——这不是模型能力的失败,而是授权架构的失败。共识的缓解手段是三条:给每个 Agent 独立凭证、令牌短时且 scope 收窄、动态(而非静态)scope;以及对不可逆/高价值操作引入 human-in-the-loop。指望 Agent 自己「识别」恶意指令并不可靠,防线要建在架构上。
为什么这在当下尤其要紧:非人类身份(NHI)的规模
非人类身份(NHI)的数量早已远超人类身份,且在快速增长。多份 2025 年公开报告口径不同,但数量级一致:CyberArk《2025 Identity Security Landscape》称组织内机器身份∶人 ≈ 82∶1(报告发布稿);Entro Labs《NHI & Secrets Risk Report H1 2025》在云原生环境下测得约 144∶1,同比上升 56%(报告要点)。具体比值取决于「什么算 NHI、采样哪类环境」,应作为区间参考而非精确值,但方向一致:当每个人对应几十上百个机器身份、且相当比例权限过大时,「部署时定死的静态大权限」就是默认的漏洞面。 这正是交集规则、短时令牌、令牌保管库成为共识的现实动因。
回到 AgentCore Identity 与该参考实现的定位
放到这个共识框架里,前面讲的 AgentCore Identity 与这个飞书参考实现,恰好落在正确的一侧,也暴露了它做对与仍需补齐的地方:
- 令牌保管库避免裸密钥暴露给推理层 —— 共识第 6 条,AgentCore Identity Token Vault(以及 Okta/Auth0 Token Vault、Entra 的托管凭证)正是这个标准机制。
- 该参考实现对下游令牌做了 scope 收窄(
drive:drive docx:document offline_access)——这其实就是「交集规则」的一半:不是把用户的全部权限原样交给 Agent,而是用户权限 ∩ Agent 声明所需的 scope。这一点做对了,只是过去没被点名。 - 仍需注意「天花板问题」:若允许高权限用户使用,仅靠「以用户身份 + scope 收窄」仍可能偏宽,严格场景下应叠加 Agent 侧的功能授权(见后文「什么时候仍需 Role」)。
有共识,也有仍未落定的部分
强共识(跨厂商一致):① 别裸用服务账号 + 静态大权限;② 也别裸转发用户原始令牌(受众/scope 不对,仍是 confused deputy);③ 给 Agent 独立的一等身份;④ 用令牌交换(RFC 8693)或等价 OBO 传身份并逐跳收窄;⑤ 有效授权 = 交集,调用时算;⑥ 用令牌保管库隔离裸密钥。
尚未落定:面向 Agent 的前置同意还没有成型标准——IETF 草案 draft-oauth-ai-agents-on-behalf-of-user(WSO2 作者,截至 -02 版仍是 Internet-Draft、非 RFC,在 IETF 标准流程中尚无正式地位)提出 requested_actor/actor_token 等新参数,正是因为 RFC 8693/7523 假设中间方是「转发用户意图」的确定性服务,而自主 Agent 是「自己创造意图」的主体——这是尚未解决的概念鸿沟。跨厂商多跳委托链(Agent A→B→C)也还有多套不互通的实现。此外,针对提示注入的混淆代理人防御在模型层无解,只能靠架构围堵。该参考实现遇到的 Gateway 无法为 CustomOauth2 provider 做每用户 OBO(见其仓库 README 与所引的 AWS 官方 samples issue),正是「架构共识已定、但工具链尚未对每种 provider 都补齐」的一个具体实例。
与「自建凭证保管」的对比
在没有 AgentCore Identity 之前,要实现「Agent 以用户身份访问下游」,通常得自己搭一套:
| 能力 | 自建方案 | AgentCore Identity |
|---|---|---|
| 每用户令牌存储 | 自己写(如往 Secrets Manager 按 userId 存) | 托管 Token Vault,按 userId 键控 |
| OAuth 3LO 授权流 | 自己实现 authorize / callback / 令牌交换 | 内置 3LO / OBO 流程 |
| 令牌自动刷新 | 自己写过期检测 + refresh 逻辑 | 托管自动刷新 |
| 按调用者注入正确令牌 | 自己在中间层路由 | 托管注入 |
自建方案不是不能做,而是:它触碰的是敏感凭证 + 容易写错的刷新/隔离逻辑,一旦某处串了用户,就是跨用户数据泄露。把这层交给托管服务,是用「少写一堆易错的基础设施」换「更强的安全默认值」。
一个真实世界的注脚:理想架构与工程现实
值得诚实地记下一点:AgentCore 的设计里,「取出用户令牌并注入到下游调用」这一步,理想情况下由 Gateway 自动完成——业务代码几乎无感。
但对非标准 OAuth 的下游(需要注册为 CustomOauth2 provider 的系统),Gateway 的自动注入路径目前存在缺陷:面向最终用户的每用户授权流不会正常触发。这种情况下的可行做法,是由 Agent 自己驱动授权流——直接调用 Identity 的数据面接口(如 GetWorkloadAccessTokenForUserId + GetResourceOauth2Token)从 Vault 取令牌,再自行注入到下游调用。
要强调的是:这只改变了「注入」那一步的实现方式(从 Gateway 自动 → Agent 手动),核心价值定位并未改变——令牌依然由托管 Token Vault 按用户保管、刷新,授权判定依然完全下放给下游系统。换句话说,即便在 Gateway 自动注入不可用时,「每用户凭证托管 + 下游裁决」这个本质仍然成立。这也提醒我们:评估一项托管能力时,既要理解它的理想架构,也要摸清它在你的具体下游上的实际边界。
落地参考:一个开源项目里的关键实践
上面的「Agent 侧驱动 3LO」不是纸上谈兵。以飞书(Lark)作为身份与资源系统的参考实现 sample-lark-identity-on-agentcore-native 就是这么落地的:用户从飞书机器人聊天进入,每条消息解析成 lark:{open_id} 身份,下游工具以该用户身份访问飞书,由飞书裁决权限。其中几段代码体现了值得复用的模式。
实践一:用稳定用户 ID 一步取令牌,取不到就返回授权链接
核心只有两个调用:先用用户身份换工作负载令牌,再用它向 Token Vault 要该用户的下游令牌。关键在于「取不到」时不报错,而是返回授权 URL——把「需要用户同意」当成正常分支,而非异常。
def get_user_lark_token(actor_id: str) -> tuple[str, str]:
"""已入库 → ("token", <令牌>);未入库 → ("auth_url", <授权链接>)。"""
wat = agentcore.get_workload_access_token_for_user_id(
workloadName=WORKLOAD, userId=actor_id # actor_id = "lark:{open_id}"
)["workloadAccessToken"]
resp = agentcore.get_resource_oauth2_token(
workloadIdentityToken=wat,
resourceCredentialProviderName=PROVIDER,
scopes=SCOPES,
oauth2Flow="USER_FEDERATION", # 每用户流,而非应用级
customState=b64url(actor_id), # 把用户身份带进授权回调
)
if resp.get("accessToken"):
return "token", resp["accessToken"]
return "auth_url", resp["authorizationUrl"]
值得学的点:
oauth2Flow="USER_FEDERATION":这是「每用户」而非「应用共享凭证」的开关。用错成应用级流,就退回了「万能钥匙」。- 令牌不落地在业务代码里:业务只拿到「用还是去授权」两种结果,令牌的存储/刷新全在 Vault,业务无需持久化任何凭证。
customState携带用户身份:授权回调回来时,靠它把这次同意归属到正确的用户,完成会话绑定。
实践二:非阻塞的首次授权,别让一次对话卡在等用户点同意
聊天是「一问一答」的,Agent 不能为了等用户在浏览器里完成授权而把一整轮对话挂住几分钟。这个项目的做法是:首次访问且令牌未入库时,把授权链接作为回复发给用户、结束本轮;用户同意后,下一轮自然就能取到令牌继续。
# Agent 一侧:第一轮
kind, value = get_user_lark_token(actor_id)
if kind == "auth_url":
return {"needs_auth": True, "auth_url": value} # 发链接,结束本轮,不阻塞
# 下一轮 kind == "token",正常调用下游
这里还有一个细腻的体验优化:入口层(接收飞书消息的 Router)在发出授权链接后,有界地轮询 Vault 一小段时间(如 45 秒),用户若很快点完同意,就无需重发消息即可直接得到答案;超时则回退到「同意后再发一次」。注意是有界轮询、且只在入口层做——绝不在授权发起的同一进程里长轮询等待,那会破坏会话绑定、也会拖垮请求。
# 入口层:发出链接后,有界地等一小会儿,提升体验(超时则让用户重发)
def wait_for_consent(actor_id: str) -> bool:
deadline = time.monotonic() + AUTH_WAIT_SECONDS # 45s,可配
while time.monotonic() < deadline:
if user_token_vaulted(actor_id):
return True
time.sleep(...)
return False
实践三:身份从入口一路透传,全程只用同一个稳定 ID
从飞书 webhook 解析出 open_id 后,立刻规整成稳定标识 lark:{open_id},此后每一环都用它:入口调用 Runtime 时作为 runtimeUserId 传下去,Agent 用它取每用户令牌,授权回调用它完成绑定。不发明第二套用户 ID,不在中途做易错的身份转换——单一稳定身份贯穿始终,是这套模型能保持「零越权」的前提。
实践四:凭证隔离到「用户 ↔ 令牌」这一对,别让传输凭证和用户令牌抢通道
一个容易踩的坑:调用下游 Runtime 需要 SigV4 签名(传输层「谁在调用」),而用户的下游令牌又要传给下游容器(「以谁的身份访问」)——两者若都塞进 Authorization 头会互相打架。这个项目的做法是分离通道:SigV4 走标准签名通道,用户令牌走一个自定义透传头,互不占用。
headers = {"X-Amzn-Bedrock-AgentCore-Runtime-Custom-Lark-Token": lark_token}
# 传输鉴权用 SigV4(标准通道),用户令牌走自定义头,两者不争 Authorization
这个模式的普适启示:「调用者是谁」和「代表谁访问」是两层身份,设计时要给它们各自的通道,不要挤在一个字段里。
什么时候你仍然需要自己的 Role (RBAC)
AgentCore Identity 把「授权」下放给了下游资源系统。这在「Agent 只是代用户访问其本就有权访问的数据」时,是最干净的模型。
但如果你的诉求是在应用自己这一侧定义「谁能用哪些 Agent 能力」——比如「只有管理员角色能调用某个高风险工具」「普通用户只能用只读工具」——那这套下游裁决模型并不覆盖它。这种应用级的功能授权,仍然需要你自建一层 RBAC: 在 identity 和 permission 之间自己引入一层 role 映射(比如一张 user → role → 允许的工具 表),在 Agent 或入口层做判定。
也就是说:
- 访问下游「数据」的授权 → 交给 AgentCore Identity + 下游系统,不必自建。
- 使用 Agent 自身「功能」的授权 → 如果需要细粒度控制,仍需自建 RBAC。
两者不冲突,分工清晰:下游裁决管数据隔离,RBAC 管工具/功能隔离。
小结
- Identity / Token / Role 是三件事:身份是「你是谁」,令牌是「你可验证的通行证」,角色是「你被允许做什么」。认证与授权要分开看。
- AgentCore Identity 的核心价值,是一个按用户维度托管下游凭证的 Token Vault——自动获取(3LO/OBO)、自动刷新、按调用者注入正确的那个用户的令牌。它不止「透传身份」,更「托管每用户的钥匙」。
- 授权判定下放给下游资源系统:Agent 以用户本人身份访问,能拿到的恰好是这个用户本来能拿到的。零越权、审计真实、权限自动同步,且 Agent 不持有共享凭证、不自建授权模型。
- 「用户身份 vs 服务身份」是伪二选一:业界共识是交集规则——Agent 要有自己独立的一等身份,用户身份也要透传,而每次调用的有效权限 = Agent 自身 scope ∩ 用户实时权限,在调用时计算。裸服务账号(环境权限)和裸转发用户令牌(混淆代理人)都是被明确反对的反模式。该参考实现对下游令牌做的 scope 收窄,正是交集规则的一半。
- 理解它的边界:Gateway 自动注入在非标准 OAuth 下游上有实际限制,可用 Agent 侧驱动作为等效方案;应用级功能授权(RBAC)仍需自建,尤其在允许高权限用户使用、需规避「天花板问题」时。
一句话:AgentCore Identity 让 Agent 从「拿一把万能钥匙替所有人办事」,变成「以每个用户本人的身份、拿他自己的钥匙办他自己的事」。 身份的归身份,授权的归下游,Agent 自己不多持有一分权限——这正是企业级 Agent 该有的身份姿态。