授权服务器作为工具范围MCP访问的PDP:受众绑定令牌和确定性PEP实施
*作者:大卫·图比亚|日期:2026年2月20日*
摘要
基于模型上下文协议(MCP)构建的代理系统倾向于继承熟悉的OAuth失败模式:授权在MCP服务器边界进行验证,但不在单个工具边界进行验证。在实践中,一旦MCP受众接受了访问令牌,就可以使用各种工具。在代理链中,这打破了最小特权,使代理成为一个混乱的代理:概率规划器现在拥有调用服务从未打算授予的操作权限。
本文将实际问题重新定义如下:如何在不将策略决策点(PDP)嵌入每个网关和MCP服务器的情况下实现高效、工具范围的访问控制?可部署的答案是让授权服务器/IdP在令牌发放时充当PDP。访问令牌成为有签名的“决策工件”,同时:
- 资源绑定:受保护的资源编码在
aud(根据RFC 8707的规范URL)。 - 工具绑定:允许的MCP工具编码在
scope作为精确的工具标识符。
网关和MCP运行时成为轻量级策略执行点(PEP):它们验证JWT(RFC 9068),通过将请求主机规范化为资源标识符来强制受众成员资格,并在MCP JSON-RPC工具名称之间执行精确匹配(params.name)以及一个作用域令牌。我们涵盖了使用OAuth 2.0令牌交换(RFC 8693)的委托、多资源事务(多受众) aud[]),以及单个网关面对多个MCP服务器的多路复用/联合场景。我们提供确定性匹配规则、完整的允许/拒绝演练和一套一致性测试向量。
1.范围、假设和威胁模型
本文有意关注后端客户端应用程序(机密客户端)和代理运行时。我们不假设最终用户在场或同意UI存在。想象一下,一个调度的批处理作业、一个内部服务或一个调用代理来编排工具的平台工作流。
1.1范围
- 客户端应用程序向授权服务器(AS)进行身份验证并获得令牌。
- 客户出于特定目的(业务运营)呼叫代理。
- 代理通过工具调用调用MCP服务器(
tools/call),可选择使用tools/list. - 授权是按工具执行的,而不仅仅是按MCP服务器执行的。
1.2超出范围(但已注明)
- 人在环审批。
- 提示安全和内容过滤(重要,但正交)。
- 除了简短的建议之外,还有完整的持有证明(DPoP/mTLS)。
- 完整的MCP服务器注册表治理(我们只涵盖authZ所需的内容)。
1.3我们关心的威胁
- 超权限/爆炸半径:对MCP服务器有效的令牌意味着可以访问许多工具。
- 困惑的代理人:代理人拥有比客户预期更广泛的特权,并“为”客户执行操作。
- 通过计划错误滥用工具:代理选择了错误的工具(例如,执行
payments.transfer而不是list.accounts). - 提示诱导工具滥用:输入操纵代理调用它不应该调用的高风险工具。
- 工具枚举:代理(或控制它的攻击者)列出所有工具和枢轴。
1.4威胁模型和缓解措施
本文假设了现代OAuth/OIDC基础(机密客户端、无处不在的TLS、短期访问令牌)。我们所针对的失败模式不是“无身份验证”,而是“在服务器边界有效但在工具执行时过于宽泛的身份验证”。在代理链中,这种差距是即时诱导的虐待和混乱的代理行为存在的地方。
以下缓解措施有意偏向于使用单个PDP(授权服务器/IdP)生成的令牌在PEP(网关、代理运行时、MCP服务器)上进行确定性的本地执行。目标是一个简单且可验证的契约:签名的令牌是一个决策工件,PEP在请求上下文(资源+工具)和令牌声明之间进行精确匹配。
*工具范围MCP授权的威胁和缓解措施。*
| 威胁(MCP认证/认证) | 主PEP/PDP控制 | 二次(可选硬化) |
|---|---|---|
| 提示诱导工具滥用 | 执行前策略检查(PEP调用PDP):将计划器输出视为不可信;根据架构验证工具参数,并强制执行每个工具/操作的允许策略 | HITL用于高权限或不可逆操作 |
| 混淆代理(令牌传递/传递特权) | 在每次调用时验证OAuth2.1/OIDC(iss/aud/exp/sig)+禁止令牌传递;使用OBO/令牌委托 | 集中策略执行(网关PDP)以实现一致的授权/同意/工具筛选 |
| 代币重播 | 短期、范围狭窄的代币;在执行工具/资源之前重新验证每次调用 | jti/随机数跟踪+发送方约束令牌(例如,在可行的情况下,DPoP/mTLS) |
| 观众滥用 | 严格 aud 在执行任何工具之前在政治公众人物处执行 | 签署意图约束(主体、受众、目的、会话);拒绝不匹配 |
| 命名混乱/工具别名冲突 | 强制使用完全限定的工具标识符,并在歧义时失败关闭 | 要求明确的消歧和/或重新同意歧义的解决方案 |
| 范围升级(委托链/权限爬行) | 具有明确权限边界的任务范围、有时限的权限;通过集中式PDP进行每次操作授权 | 将权限绑定到主题/资源/目的/持续时间;除非意图被重新验证,否则禁止特权继承 |
2.背景
2.1 MCP工具概览
MCP定义了一个工具模型,其中服务器公开了客户端可以发现和调用的工具。该协议通常使用JSON-RPC 2.0消息。两种相关方法是:
tools/list:查找可用工具tools/call:调用工具,通常使用params.name和params.arguments
2.2 OAuth构建块
我们建立在四个OAuth/IETF原语之上:
- 承载令牌使用(RFC 6750):访问令牌通过TLS提供给资源。
- JWT访问令牌配置文件(RFC 9068):JWT编码访问令牌的一种广泛使用的配置文件。
- 资源指示符(RFC 8707):客户端使用
resource参数;AS可以将令牌受众约束到该资源。 - 令牌交换(RFC 8693):客户端将一个令牌交换为另一个令牌(通常用作OBO),以获得下游调用的缩减令牌。
可选:
- 富授权请求(RFC 9396):通过
authorization_details. - 受保护的资源元数据(RFC 9728):资源发现,相关,因为MCP授权指南鼓励元数据发现和动态客户端注册。
2.2.1术语: resource vs智威汤逊 aud vs代币交换 audience
这三个词在实际实现中混合在一起,这就是隐藏微妙错误的地方:
resource是OAuth请求参数(RFC 8707)。客户端使用它向授权服务器请求用于特定受保护资源(在我们的例子中:特定的MCP服务器或MCP网关)的访问令牌。aud是JWT声明(并且是JWT访问令牌配置文件RFC 9068所要求的)。授权服务器将目标受保护资源标识符编码为audMCP网关/服务器通过成员身份强制执行:必须存在其自己的规范标识符(字符串或数组)。audience是可选的令牌交换(RFC 8693)请求参数。它与RFC 8707不同resource,不同的授权服务器对它的解释不同(有时作为客户端标识符,有时作为资源提示,有时被忽略)。
对于MCP,本文使用一致的约定:
- 使用RFC 8707
resource当你的意思是“为这个MCP服务器铸造一个令牌”时。 - 对待JWT
aud作为政治公众人物的执法表面。 - 仅提及代币交换
audience当你明确地谈论RFC 8693参数时,并解释为什么你的AS需要它(大多数设计都不需要)。
2.3 MCP 2025-11-25:对工具范围的身份验证很重要的细节
本文与MCP规范修订版2025-11-25保持一致。对于后端到代理到MCP的设计,四个细节最重要:
- 工具调用是显式协议消息。A.
tools/call请求中携带了工具标识符params.name这为我们提供了一个稳定的、可解析的授权原语。
- 工具标识符具有命名指导。MCP规范建议将工具名称视为区分大小写,1-128个字符,并限制为严格的ASCII集(字母/数字/下划线/连字符/点)。这使得“范围标记==工具名称”变得可行,而不会引起Unicode的混淆。许多部署仍然选择在网关处强制使用规范形式(例如小写)作为强化和治理措施;如果这样做,请将非规范变体视为DENY,并将其作为工具合同的一部分进行记录。
- 授权模型是OAuth原生的。MCP授权指南依赖于受保护的资源元数据(RFC 9728)进行发现,并通过OAuth依赖于受众绑定的访问令牌
resource指示符(RFC 8707)。在实践中,MCP服务器(或网关)必须拒绝不是专门为其规范资源URI铸造的令牌(通常通过aud).
- 该规范明确支持挑战驱动的增量授权。当客户端缺少作用域时,资源可以响应
WWW-Authenticate描述所需的范围。对于机器客户端,这变得具有确定性:重新运行OAuth(或令牌交换)以获得一个范围恰好与当前意图所需的工具集一致的令牌。
此外:访问令牌不得通过URL查询参数发送。使用 Authorization: Bearer 头球
具体信封(HTTP+JSON-RPC):
POST /mcp HTTP/1.1
Host: mcp-gw.example.com
Authorization: Bearer
{
"jsonrpc":"2.0",
"id":7,
"method":"tools/call",
"params":{
"name":"payments.transfer",
"arguments":{...}
}
}工具范围的执行最终取决于一个领域: params.name.
3.实际问题:不嵌入PDP的工具级访问控制
考虑一个典型的后端流程:
- 客户端获得MCP服务器的访问令牌(使用
resource=;用aud绑定到该规范资源)。 - 代理使用该令牌连接到MCP服务器。
- MCP服务器检查令牌有效性和
aud会员。 - 代理现在可以调用服务器公开的任何工具。
这打破了最小的特权。生产中:
- 工具选择是概率性的,对提示很敏感。
- MCP服务器可能会暴露读、写、管理、破坏性工具。
- 代理运行时是一个复杂的软件系统,而不是一个受信任的主体。
我们希望令牌表明:“此调用者可以为此资源、此意图执行这些工具。”
4.设计目标和不变量
4.1不变量A:资源绑定是规范的(resource -> aud)
MCP网关(或MCP服务器)上使用的令牌必须绑定到该受保护的资源:
- 客户端使用RFC 8707请求令牌
resource指示符(规范MCP基本URL)。 - 授权服务器将所选资源标识符编码到JWT中
aud索赔。 - 网关/服务器通过精确匹配(成员资格
aud是一个数组)。
避免将其与令牌交换混合使用 audience 除非您明确依赖RFC 8693参数,并且您已经记录了授权服务器如何将其映射到 aud (行为因产品而异)。
4.2不变性B:工具权限明确且可执行
用于MCP工具调用的令牌必须对允许使用的工具进行编码。陈述:
- 选项1(简单):
scope包含精确的工具名称(空格分隔)以及任何通用作用域。 - 选项2(更安全):结构化索赔,如
tool_permissions. - 选项3(最具表现力):RAR
authorization_details用约束表示工具权限。
强制必须是确定性的:精确匹配,默认情况下没有通配符。
4.3不变量C:降档是单调的
当代理获得MCP网关的OBO令牌时,得到的令牌必须是调用者对特定意图的有效特权的子集。
4.4不变量D:强制执行是外部化的(最好)
不应在每个MCP服务器和代理中重新实施工具强制。首选验证JWT并执行工具规则的网关(PEP)。
5.建筑模式
5.1网关强制授权(代理网关+MCP网关)
我们模拟了两个执行边界。两者都是 政治公众人物 (策略执行点):它们对请求和令牌执行确定性验证。这 AS/IdP仍然是PDP (策略决策点),通过决定在发行时哪些索赔出现在代币中。
- PEP#1(代理网关,推荐): 位于代理运行时的前面。它强制调用者持有为 代理资源 通过验证JWT并要求 观众比赛 与网关的规范RFC 8707资源URL相反。这使得身份验证/授权不在代理代码范围内(开发人员不会意外成为安全工程师)。
- PEP#2(MCP网关,推荐): 位于MCP服务器前面。它验证JWT并执行 工具级授权 通过将请求的MCP工具名称与AS发出的声明(范围/tool_permissions)进行匹配。
在实践中,PEP#1和PEP#2可以部署为 两个独立的网关 或作为a 单一共享网关 有两个合乎逻辑的政策。密钥不变量是相同的: *为被调用的资源铸造令牌,PEP进行确定性检查*.
选项A:两个网关(不同的受众)
+-----------------------------+
| Authorization Server (AS) |
| - OAuth/OIDC |
| - PDP: roles -> claims |
| - Token Exchange (OBO) |
+--------------+--------------+
^
|
(1) token for agent | (2) token exchange for MCP (downscoped)
|
+-------------------+ +-----+-----------+ +-------------------+
| Client Backend |---->| PEP #1 |----->| Agent Runtime |
| (confidential) | | (Agent GW) | | - planner/tool use |
+-------------------+ +-----+-----------+ +---------+---------+
|
| (3) MCP JSON-RPC
v
+------+------+
| PEP #2 |
| (MCP GW) |
+------+------+
|
v
+------+------+
| MCP Server |
| (tools) |
+-------------+选项B:一个共享网关(两个逻辑PEP角色)
当您更喜欢单个受管数据平面时,可以运行一个网关并发布两个 资源标识符 (RFC 8707)在同一主机上(路径范围的资源很好):
- 代理API资源:
https://gw.example.com/agent - MCP API资源:
https://gw.example.com/mcp(可选按上游:https://gw.example.com/mcp/或https://gw.example.com/mcp/)
+-----------------------------+
| Authorization Server (AS) |
| - OAuth/OIDC |
| - PDP: roles -> claims |
| - Token Exchange (OBO) |
+--------------+--------------+
^
|
+-------------------+ AT_agent | +-------------------+ /agent,/mcp +-------------+
| Client Backend |-----------+---->| Unified Gateway || Agent |
| (confidential) | (aud=/agent) | - PEP #1 (/agent) | (AT_agent / | Runtime |
+-------------------+ | - PEP #2 (/mcp) | AT_mcp) +-------------+
+---------+---------+
|
| proxy /mcp JSON-RPC (PEP #2)
v
+-----+------+
| MCP Server |
| (tools) |
+------------+此部署加强了治理:安全团队拥有一个单一的网关策略平面 两者 代理API和MCP API,而AS/IdP仍然是 *谁可以得到哪些索赔*.
此选项的关键特性是 没有直接代理到MCP的连接代理运行时和MCP服务器都位于隔离的网络中,只能从网关访问(例如使用mTLS、ACL、私有路由或服务网格标识)。流程为:
- 客户端->GW(/代理): 客户端请求
AT_agent随着aud=https://gw.example.com/agent和aintent_id,然后调用网关上的代理端点。PEP#1验证令牌(发行者、签名、exp/nbf、,aud)并代理到代理运行时。 - 代理->GW(/mcp): 当代理决定需要工具时,它会获取/使用
AT_mcp随着aud=https://gw.example.com/mcp和scope包含允许的工具ID,然后调用/mcp在同一个网关上。PEP#2强制执行工具级范围(以及任何租户/命名空间规则),并将JSON-RPC调用代理到MCP服务器。
5.1.1网关资源标识符与上游路由(由谁验证 aud?)
共享网关部署中一个微妙但重要的问题是:什么是 *受保护资源* 为了RFC 8707的受众限制?是不是 网关URL 呼叫者(客户端或代理)呼叫,或 上游代理/MCP服务器 最终执行请求?
这很重要,因为受众验证是强制性的:OAuth Security BCP要求访问令牌仅限于特定资源服务器(或者,如果不可行,则限制为一小部分)的受众,并要求每个资源服务器在每次请求时验证预期的受众(RFC 9700)。资源指示符(RFC 8707)同样假设客户端请求其打算使用的位置的令牌,并警告对多个资源/受众有效的访问令牌需要接收者之间的高度信任。
如果你用 aud=https://gw.example.com/mcp/acme (因为客户端正在调用该网关端点),然后转发 *同一持票人代币* 连接到预期的上游MCP服务器 aud=https://mcp-acme.example.com/mcp,上游将(正确地)拒绝受众不匹配的请求。这不是bug;它是按设计工作的机制。
在实践中,您有三种可部署的模式。“最正确”的选择不是哲学上的:这取决于你是否希望上游成为一流的OAuth资源服务器,以及你想在多大程度上推动深度防御。
*RFC 8707风格资源标识符的上游受众模式网关。*
| 模式 | 代币的目标是什么 | 备注(安全+操作) |
|---|---|---|
| P1。Gateway是OAuth资源服务器(终止于GW) | aud = gw URL (每个代理/MCP端点的路径范围) | 上游不可公开访问;GW执行 aud/scope/工具声明和内部路线。使用mTLS/ACL/服务网格保护GW到上游。最简单,通常更受欢迎。 |
| P2。网关执行令牌交换(下游令牌中介) | 入站: aud = gw;下游: aud = upstream | GW验证入站令牌,然后使用RFC 8693令牌交换为上游资源(可选缩减)铸造一个新的短期令牌。实现纵深防御。增加延迟/AS负载。需要严格的同族主义者和降低应对策略,以防止交换滥用。 |
P3。多受众代币(aud[] 包括GW和上游) | aud = [gw, upstream] | JWT和一些AS策略是可能的,但会增加爆炸半径。RFC 8707警告说,多受众承载令牌需要接收者之间的高度信任;RFC 9700建议将受众限制为特定的RS或一小部分。只考虑上游无法直接到达且信任边界很强的情况。 |
模式P1:网关终止OAuth;上游是私有的
这是经典的API网关模型:网关是资源服务器,上游是内部组件。访问令牌是为网关的规范URL(客户端实际调用的URL)铸造的,只有网关执行OAuth验证。上游受网络层控制(mTLS、ACL、专用子网)保护,并信任网关。
关键属性:
- 令牌仅在网关URL处可用(路径范围的资源标识符很好)。
- 上游从不直接接受客户发行的不记名代币(他们要么永远看不到,要么忽略它们)。
- 上游的妥协价值较低,因为它不能直接从网关边界外访问。
说明性跟踪(资源指标+工具范围):
请求:代理->AS:token_exchange(目标GW MCP资源)
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=AT_agent&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
resource=https://gw.example.com/mcp/acme&
scope=mcp.call_tool inventory.get quote.read&
intent_id=ord-2026-000125响应:AS->代理:AT_mcp_gw(解码的JWT草图)
{
"iss":"https://as.example.com",
"sub":"agent_runtime",
"aud":"https://gw.example.com/mcp/acme",
"exp":1760669100,
"scope":"mcp.call_tool inventory.get quote.read",
"tool_permissions":[
{"tool":"inventory.get","actions":["invoke"]},
{"tool":"quote.read","actions":["invoke"]}
],
"act":{"sub":"agent_runtime","typ":"service"}
}请求:代理->GW(/mcp/acme):工具/调用(令牌目标GW)
POST /mcp/acme HTTP/1.1
Host: gw.example.com
Authorization: Bearer AT_mcp_gw
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"p1-1",
"method":"tools/call",
"params":{
"name":"inventory.get",
"arguments":{"sku":"X-42"}
}
}此时,GW强制执行 aud (与规范的GW URL完全匹配)和工具范围(与 inventory.get).然后,GW通过专用信道(mTLS、ACL)将请求代理到上游MCP服务器。上游确实如此 *不* 需要验证客户端令牌,因为在这种模式下,它不是OAuth资源服务器。
模式P2:网关对上游资源执行令牌交换
如果你想让上游也验证OAuth令牌(深度防御、更强的租户边界或独立的上游所有权),GW可以充当一个机密的OAuth客户端,并创建一个 *上游特定* 通过RFC 8693令牌交换。
在操作上,GW:
- 验证入站令牌的自身资源标识符,
- 使用入站令牌执行令牌交换
subject_token, - 请求一个新的令牌
resource=以及范围的严格子集, - 使用交换的令牌转发到上游。
RFC 8693明确涵盖了这一点:资源服务器可以承担客户端的角色,并将传入的访问令牌交换为后端服务可用的令牌。
说明性痕迹:
请求:GW->AS:token_exchange(薄荷上游代币)
POST /oauth2/token HTTP/1.1
Host: as.example.com
Authorization: Basic Z3c6Li4u % gw client creds
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=AT_mcp_gw&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
resource=https://mcp-acme.internal.example.net/mcp&
scope=mcp.call_tool inventory.get&
intent_id=ord-2026-000125响应:AS->GW:AT_mcp_upstream(解码的JWT草图)
{
"iss":"https://as.example.com",
"sub":"agent_runtime",
"aud":"https://mcp-acme.internal.example.net/mcp",
"exp":1760668860,
"scope":"mcp.call_tool inventory.get",
"act":{"sub":"gw.example.com","typ":"pepgateway"}
}请求:GW->上游MCP:工具/调用(令牌目标上游)
POST /mcp HTTP/1.1
Host: mcp-acme.internal.example.net
Authorization: Bearer AT_mcp_upstream
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"p2-1",
"method":"tools/call",
"params":{
"name":"inventory.get",
"arguments":{"sku":"X-42"}
}
}P2的安全注意事项:
- 将代币交换视为特权操作。强制执行同位语(哪些客户端可以交换,哪些目标是允许的)和严格的降策略。否则,代币交换将成为一种横向运动的原始形式。
- 积极缓存交换的令牌(短TTL,每个(主题、意图、目标、范围)元组),以控制延迟和AS负载。
- 将GW的发送方约束令牌(mTLS/DPoP)优先于上游令牌,以降低重播风险。
模式P3:铸造多受众访问令牌(aud[] 包含GW和上游)
JWT许可证 aud RFC 9068将JWT语义携带到访问令牌中。一些部署会生成一个在GW URL和上游URL都有效的令牌:
"aud": ["https://gw.example.com/mcp/acme", "https://mcp-acme.example.com/mcp"]这使得GW和上游都可以接受相同的令牌。这可能是实用的,但它扩大了代币泄漏的爆炸半径:任何持有者都有可能在以下情况下使用代币 *要么* 观众。RFC 8707明确警告说,多受众访问令牌需要受众之间的高度信任,RFC 9700建议将受众限制到特定的资源服务器(或一小部分),作为防止令牌钓鱼和泄漏的核心缓解措施。
如果您采用P3,请将其视为受控环境的优化,并将其与强有力的缓解措施相结合:
- 上游不能公开访问(否则您可以启用网关的旁路)。
- 在可能的情况下使用发送方约束的令牌。
- 保持TTL简短,并将令牌绑定到交易窗口(
intent_id,jti等等)。 - 使用资源限定工具权限(见第6.3节),以避免在以下情况下出现混淆的代理行为
aud[]涵盖多种受保护的资源。
PEP#1(代理网关)的最低强制执行
- 验证JWT签名和标准声明(
iss,exp/nbf,iat)并拒绝未签名/无效的令牌。 - 计算传入请求的预期资源标识符,并要求 精确匹配 其中一个令牌受众:
- 独立网关:预期 aud = https://agent-gw.example.com - 共享网关:需要 aud = https://gw.example.com/agent
- (可选但实用)需要一个粗略的范围,如
agent.invoke以防止意外使用无关的令牌。
PEP#1不需要解析MCP工具有效载荷。工具级授权在PEP#2强制执行。
5.2拒绝路径图(刀具不匹配)
这正是我们想让人厌烦的失败模式。
JWT (signed) says: tool_permissions = {list.accounts}
MCP payload says: tools/call name = payments.transfer
+--------------------+
| MCP Gateway (PEP) |
+--------------------+
| verify JWT sig
| check aud
| extract tool name
v
+-------------------------+
| requested_tool in set ? |
+-------------------------+
| YES -> forward
|
| NO
v
DENY (403 / JSON-RPC error)5.3序列视图(允许和拒绝)
允许:
(0) Client -> AS : get AT_agent (resource=agent, intent=accounts.read)
(1) Client -> Agent : call /agent (Authorization: Bearer AT_agent)
(2) Agent -> AS : token exchange (subject_token=AT_agent,
resource=mcp-gw, scope=list.accounts)
(3) AS -> Agent : AT_mcp (aud=mcp-gw, tool_permissions=[list.accounts])
(4) Agent -> MCP GW : tools/call name=list.accounts (Authorization: Bearer AT_mcp)
(5) MCP GW : validate + authorize => ALLOW
(6) MCP GW -> MCP : forwardDENY(刀具不匹配):
(4) Agent -> MCP GW : tools/call name=payments.transfer (Authorization: Bearer AT_mcp)
(5) MCP GW : validate ok, authorize fails => DENY
(6) MCP GW -> Agent : error (insufficient_tool_scope)6.授权合同
假设JWT访问令牌。我们定义了一个最小合同;您可以根据需要添加更多声明。
6.1用于调用代理的令牌(AT_agent)
aud:规范 代理网关 URL(资源指示符,RFC 8707)。如果运行共享网关,请使用路径范围的资源,例如https://gw.example.com/agent.client_id(或azp):标识后端客户端。- 可选:
intent索赔(字符串),intent_id(UUID)。
6.2代理用于调用MCP工具的令牌(AT_MCP)
aud:规范MCP网关URL(资源指示符)。- 工具权限:
- scope 包括允许的工具名称(空格分隔、精确),和/或 - tool_permissions 作为结构化权利要求。
结构化索赔示例(出于安全考虑,首选):
"tool_permissions": [
{ "tool": "list.accounts", "actions": ["invoke"] },
{ "tool": "accounts.get", "actions": ["invoke"] }
]AT_mcp的解码JWT有效载荷示例(省略签名):
{
"iss": "https://as.example.com",
"sub": "client_backend_app",
"aud": "https://mcp-gw.example.com/mcp",
"iat": 1760668800,
"exp": 1760669100,
"scope": "list.accounts accounts.get",
"tool_permissions": [
{"tool":"list.accounts","actions":["invoke"]},
{"tool":"accounts.get","actions":["invoke"]}
],
"intent": "accounts.read",
"intent_id": "e1b2f3c4-5d6e-7a8b-9c0d-1e2f3a4b5c6d",
"azp": "client_backend_app",
"act": { "sub": "agent_runtime", "typ": "service" }
}6.3多资源交易:多受众(aud[])访问令牌(有效,可选)
单个代理“业务操作”通常跨越多个受MCP保护的资源。示例:从“账户MCP”(MCP-A)读取账户,然后通过“支付MCP”(MCP-B)执行支付。朴素的实现要求(或交换)每个MCP服务器一个令牌。这是可移植的,但它会在代理运行时造成令牌流失和复杂性。
在JWT aud (观众)声明可以是单个字符串或字符串数组。资源服务器验证 aud 通过检查其自身的标识符是否存在(成员资格),而不是通过假设 aud 总是一个单一的值。这由JWT(RFC 7519)定义,并包含在JWT访问令牌配置文件(RFC 9068)中。
在OAuth术语中,MCP 2025-11-25依赖于资源指示符(RFC 8707):客户端通过发送 resource 参数。RFC 8707允许多个 resource 但是它并没有强制AS必须发出覆盖所有资源的单个令牌。返回的是AS的政策决定:
- AS可以为每个资源(最便携的)发放一个令牌,
- 或者它可以发出一个JWT访问令牌
aud作为覆盖多个规范资源标识符的数组, - 或者它可以拒绝多资源请求。
本节讨论 aud[] 令牌是一种有效的、可选的优化,可以在控制良好的环境中证明其合理性,同时通过将权限绑定到(资源、工具)来保留最小的特权。
6.3.1为什么 aud[] 在受控环境中值得考虑
在一个强化的后端到后端环境中(机密客户端、严格的网络边界、短期令牌), aud[] 可以在不削弱政策模型的情况下减少运营摩擦:
- 更少的代币流失:交易过程中代币端点往返次数更少。
- 更低的AS负载:突发代理工作负载期间的交换更少。
- 更简单的代理状态:当计划需要多个下游跳时,每资源令牌缓存逻辑更少,竞争条件更少。
- 更干净的“升级”:资源可以挑战一次额外的作用域,由此产生的令牌可以覆盖当前意图窗口所需的MCP服务器集。
我们的目标不是创造一个“胖”代币。目标是制造一个短期的、事务范围的令牌,该令牌仍然受到工具权限的严格限制。
6.3.2标准观点:什么是“有效”与什么是“便携”
- 有效(JWT):
aud可以是数组。任何JWT消费者都必须处理这个问题。 - 有效(RFC 9068):JWT访问令牌遵循JWT语义,包括
aud作为字符串或数组。 - 可选(RFC 8707):多个
resource参数可能会出现,但AS行为不是固定的。如果你需要跨IdP的可移植性,假设你需要每个资源令牌,除非你控制AS和资源服务器。
实际解释: aud[] 当您控制AS(或至少其策略行为)并且愿意将其视为可以根据环境禁用的优化时,这是一种可部署的模式。
6.3.3多资源交易令牌的参考架构
合同故意严格:多资源 aud[] 令牌还必须携带资源限定的工具权限,否则许可的工具列表将成为一个令人困惑的副启用码。
(OBO / Token Exchange)
+------------------------+
| Authorization Server |
| (PDP: roles -> tools) |
+-----------+------------+
|
| issues AT_mcp_multi
| aud=[MCP-A, MCP-B]
v
+------------------------+
| Agent runtime |
| holds AT_mcp_multi |
+-----------+------------+
|
+-----+-----+
| |
v v
+-----------+ +-----------+
| MCP-A PEP | | MCP-B PEP |
| aud has A | | aud has B |
| tool(A)? | | tool(B)? |
+-----+-----+ +-----+-----+
| |
v v
+-----------+ +-----------+
| MCP-A srv | | MCP-B srv |
+-----------+ +-----------+6.3.4示例:多资源OBO(令牌交换)请求
以下是用作OBO的说明性RFC 8693令牌交换请求。代理商交换 AT_agent 对于一个短暂的 AT_mcp_multi 并通过重复向AS请求一个请求中的两个受保护资源 resource=.
重要提示:这并不能保证在每个AS上都有效。这是一个策略驱动的功能。如果不支持,则退回到按资源交换(第8.10节)。
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
subject_token=&
requested_token_type=urn:ietf:params:oauth:token-type:access_token&
resource=https://mcp-a.example.com/mcp&
resource=https://mcp-b.example.com/mcp&
scope=list.accounts payments.transfer如果作为JWT访问令牌发出,解码的有效载荷看起来像:
{
"iss":"https://as.example.com",
"sub":"client_backend_app",
"aud":[
"https://mcp-a.example.com/mcp",
"https://mcp-b.example.com/mcp"
],
"iat":1760668800,
"exp":1760669100,
"intent":"balance-and-transfer",
"intent_id":"8c9d7e6f-1111-2222-3333-444455556666",
"tool_permissions":[
{"rs":"https://mcp-a.example.com/mcp","tool":"list.accounts","actions":["invoke"]},
{"rs":"https://mcp-b.example.com/mcp","tool":"payments.transfer","actions":["invoke"]}
],
"act":{ "sub":"agent_runtime", "typ":"service" }
}6.3.5示例:两个具有相同MCP调用 AT_mcp_multi (允许)
呼叫1至MCP-A:
{
"jsonrpc":"2.0",
"id":1101,
"method":"tools/call",
"params":{
"name":"list.accounts",
"arguments":{"limit":10}
}
}MCP-A政治公众人物评估:
resource: https://mcp-a.example.com/mcp
aud contains: YES
requested: list.accounts
permitted(rs): {list.accounts}
result: ALLOW呼叫MCP-B 2:
{
"jsonrpc":"2.0",
"id":1102,
"method":"tools/call",
"params":{
"name":"payments.transfer",
"arguments":{
"from_account":"ES00-1234",
"to_account":"ES99-9876",
"amount":"250.00",
"currency":"EUR"
}
}
}MCP-B政治公众人物评估:
resource: https://mcp-b.example.com/mcp
aud contains: YES
requested: payments.transfer
permitted(rs): {payments.transfer}
result: ALLOW6.3.6示例:相同的令牌,错误的资源上的错误工具(拒绝)
一种常见的代理失败模式是将“工具意图”与“资源目标”混合在一起。即使有 aud[],令牌不得允许跨资源工具泄漏。
代理发送 payments.transfer 误操作MCP-A:
resource: https://mcp-a.example.com/mcp
aud contains: YES
requested: payments.transfer
permitted(rs): {list.accounts}
result: DENY (insufficient_tool_scope)这是关键属性:多受众并不意味着“工具是全球性的”。
6.3.7默认拒绝护栏:在以下情况下拒绝扁平工具清单 aud 拥有多种资源
如果你收养 aud[],如果令牌包含多个受众但不将工具绑定到资源,则将其视为格式错误(或拒绝)。否则,调用者可能会走私一个许可的工具列表,意外地授权对错误的资源产生副作用。
推荐规则:
if aud is array with size > 1 AND tool_permissions are not resource-qualified:
DENY (invalid_token / invalid_scope_contract)这是严格的,但它防止了一类混乱的副手错误。
6.3.8别名受众:一个逻辑资源,多个规范标识符
有时你想要多个 aud 值不是因为您有多个资源,而是因为同一网关有多个稳定的标识符(内部DNS和外部DNS,或旧基路径和新基路径)。
例子:
"aud":[
"https://mcp-gw.internal.example.com/mcp",
"https://mcp-gw.example.com/mcp"
]这通常比跨越无关资源的风险低,但仍然需要在政治公众人物中进行严格的规范化:
- 规范化方案/主机外壳,
- 规范尾随斜线规则,
- 将入站主机+路径映射到一个规范资源标识符,
- 然后根据验证成员资格
aud.
6.3.9需要更丰富约束时的替代方案
6.4一个网关,多个MCP服务器:将工具绑定到选定的上游
当令牌只针对一个MCP保护的资源时,“aud是资源,工具在范围内”的契约是最干净的。在实践中,团队通常在单个网关端点后面部署多个MCP服务器,以简化客户端配置,并保持网络和身份验证控制的集中化。
存在两种部署变体:
变体1:网关是唯一受保护的资源(aud =网关)
在这个模型中,网关是OAuth保护的资源。网关验证令牌(aud ==网关资源标识符),然后纯粹通过以下方式强制工具访问 scope 会员。上游映射工具是网关配置问题,而不是令牌问题。
当网关公开一个统一的工具名称空间时,这最有效,其中每个工具名称都是全局唯一的,例如通过在工具前加上目标标识符:
bank.list_accountsbank.payments_transfercrm.search_customers
PEP检查保持简单:请求的工具名称必须与作用域令牌完全匹配。
变体2:一个网关后面有多个上游受保护的资源(多资源语义)
在此模型中,网关仍然终止流量,但您希望IdP策略保持上游感知(例如,将“银行MCP”与“crm MCP”作为独立资源分开)。您可以通过使用额外的声明将每个工具权限绑定到上游资源标识符,使令牌保持短暂,并仍然避免在网关中运行PDP。
建议索赔形式:
"mcp_toolset": [
{"rs":"https://mcp-bank.example.com/mcp","tools":["accounts.list","payments.transfer"]},
{"rs":"https://mcp-crm.example.com/mcp","tools":["customers.search"]}
]网关执行规则:
- 确定哪个上游资源(
rs_selected)工具调用将被路由到(基于工具前缀、会话映射或路由元数据)。 - 验证
aud网关资源的成员资格(或允许aud[]如果您在中显式地对上游资源进行建模,则表示成员身份aud). - 通过选择匹配项来验证工具权限
mcp_toolset入口为rs_selected并检查工具名称的精确匹配。
这为您提供了工具权限和上游MCP服务器之间的严格绑定,而无需运行时策略查找。
多路复用时必须设置护栏
如果一个令牌可用于多个逻辑资源,则将其视为格式错误,除非工具权限是资源限定的(工具列表必须绑定到 rs).这可以防止“扁平工具列表”混淆的副故障,即一组允许的工具意外地授权对错误的上游产生副作用。
如果您需要超出“工具允许列表”(金额限制、帐户范围、地区、租户、时间窗口)的每种资源限制,请首选结构化授权:
- 丰富授权请求(RFC 9396)
authorization_details,或 - 通过令牌交换(RFC 8693)按资源令牌(最便携),或
- 网关多路复用(第12.4节):网关内每个上游策略有一个规范受众。
aud[] 当环境得到控制并且上述护栏得到加强时,它仍然是一种实用的优化。
7.执行:令牌和MCP有效载荷之间的精确工具匹配
7.1从MCP请求中提取工具名称
对于可流式传输的HTTP MCP,工具调用看起来像JSON-RPC tools/call 与:
method = "tools/call"params.name = ""
请求示例:
POST /mcp HTTP/1.1
Host: mcp-gw.example.com
Authorization: Bearer
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":101,
"method":"tools/call",
"params":{
"name":"list.accounts",
"arguments":{"limit":10}
}
}7.2政治公众人物决策算法(参考)
INPUT: jwt, mcp_request
1) Verify JWT signature (kid -> JWKS), iss, exp/nbf
2) Verify audience and resource binding:
- Compute the canonical protected resource identifier for *this* request.
Recommended: scheme=https, lowercase host, normalize default ports, and normalize the MCP base path (e.g., /mcp), following your RFC 8707 canonicalization rules.
- aud_set = {jwt.aud} if jwt.aud is a string else set(jwt.aud)
- require canonical_resource_id in aud_set (membership check; aud MAY be an array)
3) Parse MCP request:
- require jsonrpc == "2.0"
- require method in {"tools/list","tools/call"}
4) If method == "tools/call":
- requested_tool = params.name
- allowed = extract permissions from tool_permissions OR scope-derived set
- if tool_permissions are resource-qualified (e.g., each entry has `rs`):
- allowed = { t.tool | t.rs == canonical_mcp_gateway_url }
- if requested_tool in allowed => ALLOW
else => DENY (insufficient_tool_scope)
5) If method == "tools/list":
- allow but filter the tool list to allowed_tools (ALLOW+FILTER), or deny8.工作示例(确定性允许/拒绝)
每个示例都显示了解码的JWT有效载荷和MCP JSON-RPC请求。在每个字段下,都有一个紧凑的表格突出显示了中的决策关键字段 大胆.
8.1示例A(允许):正确的令牌和正确的工具调用
顺序:
client backend -> agent runtime -> MCP gateway : tools/call list.accounts (AT_mcp)
MCP gateway -> MCP server : forward
MCP server -> MCP gateway : result
MCP gateway -> agent runtime -> client backend : result解码的JWT有效载荷(说明性;省略签名):
{
"iss":"https://as.example.com",
"sub":"client_backend_app",
"aud":"https://mcp-gw.example.com/mcp",
"exp":1760669100,
"iat":1760668800,
"jti":"8ddc2a5b-5e0f-4c2f-88f3-5d1a9b8f12a1",
"intent":"accounts.read",
"tool_permissions":[
{"tool":"list.accounts","actions":["invoke"]}
]
}决策输入(标记突出显示):
| 索赔 | 价值 |
|---|---|
| 国际空间站 | https://as.example.com |
| 子 | client_backend_app |
| 澳元 | https://mcp-gw.example.com/mcp |
| 经验 | 1760669100 |
| 意图 | accounts.read |
| 工具权限 | list.accounts (invoke) |
MCP请求:
{
"jsonrpc":"2.0",
"id":1,
"method":"tools/call",
"params":{
"name":"list.accounts",
"arguments":{"limit":10}
}
}决策输入(请求要点):
| 字段 | 值 |
|---|---|
| 方法 | tools/call |
| params.name | list.accounts |
| 参数.参数.限制 | 10 |
决定:
aud matches: YES
requested: list.accounts
permitted: {list.accounts}
result: ALLOW8.2示例B(拒绝):令牌允许列表。帐户,代理尝试付款。转账
解码的JWT有效负载(与示例A相同的策略):
{
"iss":"https://as.example.com",
"sub":"client_backend_app",
"aud":"https://mcp-gw.example.com/mcp",
"exp":1760669100,
"intent":"accounts.read",
"tool_permissions":[
{"tool":"list.accounts","actions":["invoke"]}
]
}决策输入(标记突出显示):
| 索赔 | 价值 |
|---|---|
| 澳元 | https://mcp-gw.example.com/mcp |
| 工具权限 | list.accounts (invoke) |
MCP请求(代理尝试未经授权的副作用):
{
"jsonrpc":"2.0",
"id":2,
"method":"tools/call",
"params":{
"name":"payments.transfer",
"arguments":{
"from_account":"ES00-1234",
"to_account":"ES99-9876",
"amount":"250.00",
"currency":"EUR"
}
}
}决策输入(请求要点):
| 字段 | 值 |
|---|---|
| 方法 | tools/call |
| params.name | payments.transfer |
政治公众人物决定:
aud matches: YES
requested: payments.transfer
permitted: {list.accounts}
result: DENY (insufficient_tool_scope)JSON-RPC错误示例(说明性):
{
"jsonrpc":"2.0",
"id":2,
"error":{
"code":-32603,
"message":"unauthorized tool call",
"data":{
"reason":"insufficient_tool_scope",
"requested_tool":"payments.transfer",
"permitted_tools":["list.accounts"],
"aud":"https://mcp-gw.example.com/mcp",
"intent":"accounts.read"
}
}
}8.3示例C(拒绝):令牌允许列表。帐户,有效载荷使用payments.payment
解码的JWT有效载荷:
{
"iss":"https://as.example.com",
"sub":"client_backend_app",
"aud":"https://mcp-gw.example.com/mcp",
"exp":1760669100,
"tool_permissions":[
{"tool":"list.accounts","actions":["invoke"]}
]
}MCP请求:
{
"jsonrpc":"2.0",
"id":3,
"method":"tools/call",
"params":{
"name":"payments.payment",
"arguments":{
"amount":"12.34"
}
}
}决策输入(亮点):
| 字段 | 值 |
|---|---|
| 澳元 | https://mcp-gw.example.com/mcp |
| params.name | payments.payment |
| 工具权限 | list.accounts (invoke) |
决定:
aud matches: YES
requested: payments.payment
permitted: {list.accounts}
result: DENY8.4示例D(拒绝):作用域字符串不匹配(JWT表示list.accounts,payload表示payments.transfer)
一些部署直接在标准OAuth中编码工具权限 scope 声明(RFC 6750/RFC 9068风格),使用工具名称作为作用域。
解码的JWT有效载荷(说明性):
{
"iss":"https://as.example.com",
"sub":"client_backend_app",
"aud":"https://mcp-gw.example.com/mcp",
"exp":1760669100,
"scope":"list.accounts",
"intent":"accounts.read",
"client_id":"backend-billing-service"
}MCP请求:
{
"jsonrpc":"2.0",
"id":5,
"method":"tools/call",
"params":{
"name":"payments.transfer",
"arguments":{
"from_account":"ES00-1234",
"to_account":"ES99-9876",
"amount":"250.00",
"currency":"EUR"
}
}
}决策输入(亮点):
| 字段 | 值 |
|---|---|
| 澳元 | https://mcp-gw.example.com/mcp |
| 范围 | list.accounts |
| params.name | payments.transfer |
决定:
aud matches: YES
scope tokens: {list.accounts}
requested: payments.transfer
result: DENY示例响应(HTTP 403+RFC 6750风格的质询,加上JSON-RPC错误体):
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="payments.transfer",
resource="https://mcp-gw.example.com/mcp"
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":5,
"error":{
"code":-32603,
"message":"unauthorized tool call",
"data":{
"reason":"insufficient_scope",
"requested_tool":"payments.transfer",
"permitted_scopes":["list.accounts"]
}
}
}检查的ASCII视图(精确匹配,无子字符串技巧):
JWT scope: "list.accounts"
Payload: tools/call name="payments.transfer"
PEP rule: requested_tool IN split(scope, " ")8.5示例E(允许+过滤):减少枚举的工具/列表过滤
如果你允许 tools/list,不要泄露完整目录。
解码的JWT有效载荷(说明性):
{
"iss":"https://as.example.com",
"sub":"client_backend_app",
"aud":"https://mcp-gw.example.com/mcp",
"exp":1760669100,
"tool_permissions":[
{"tool":"list.accounts","actions":["invoke","list"]}
]
}请求:
{"jsonrpc":"2.0","id":4,"method":"tools/list","params":{}}决策输入(亮点):
| 字段 | 值 |
|---|---|
| 方法 | tools/list |
| 澳元 | https://mcp-gw.example.com/mcp |
| 工具权限 | list.accounts (list) |
筛选后的响应(仅限策略允许的工具):
{
"jsonrpc":"2.0",
"id":4,
"result":{
"tools":[
{
"name":"list.accounts",
"description":"List accounts",
"inputSchema":{"type":"object"}
}
]
}
}8.6示例F(否定):作用域字符串陷阱和修复
避免以下检查:
if jwt.scope contains requested_tool => allow这是不安全的(子字符串风险)。使用以下任一方法修复:
- 结构化索赔(
tool_permissions)精确匹配,或 - 严格标记化(
scope.split(" "))精确匹配。
8.7示例G(允许):一个令牌,两个MCP服务器通过 aud[] 以及资源绑定工具权限
假设代理人必须致电 两种不同的MCP保护资源 在单笔交易中:
- MCP-A(只读):
https://mcp-a.example.com/mcp - MCP-B(副作用):
https://mcp-b.example.com/mcp
授权服务器发出一个 AT_mcp_multi 与:
aud作为包含规范资源标识符的数组,以及tool_permissions明确绑定到资源的条目(rs)和工具。
解码的JWT有效载荷(说明性):
{
"iss":"https://as.example.com",
"sub":"agent_runtime",
"aud":[
"https://mcp-a.example.com/mcp",
"https://mcp-b.example.com/mcp"
],
"exp":1760669100,
"tool_permissions":[
{"rs":"https://mcp-a.example.com/mcp","tool":"list.accounts","actions":["invoke"]},
{"rs":"https://mcp-b.example.com/mcp","tool":"payments.transfer","actions":["invoke"]}
]
}决策输入(标记突出显示):
| 索赔 | 价值 |
|---|---|
| 澳元 | [MCP-A, MCP-B] |
| 工具权限 | (MCP-A, list.accounts), (MCP-B, payments.transfer) |
请求1(MCP-A):
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"list.accounts","arguments":{"limit":5}}}请求2(MCP-B):
{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"payments.transfer","arguments":{"amount":"10.00"}}}序列(两个调用,相同的令牌):
agent -> MCP-A gateway : tools/call list.accounts (AT_mcp_multi)
MCP-A gateway : check aud contains MCP-A AND tool allowed for rs=MCP-A
agent -> MCP-B gateway : tools/call payments.transfer (AT_mcp_multi)
MCP-B gateway : check aud contains MCP-B AND tool allowed for rs=MCP-B决定:
call 1: ALLOW
call 2: ALLOW8.8示例H(密度): aud[] 包括MCP-B,但该工具不允许用于MCP-B
仅允许令牌 list.accounts 在MCP-A上,但包括以下两个受众:
{
"iss":"https://as.example.com",
"sub":"agent_runtime",
"aud":[
"https://mcp-a.example.com/mcp",
"https://mcp-b.example.com/mcp"
],
"exp":1760669100,
"tool_permissions":[
{"rs":"https://mcp-a.example.com/mcp","tool":"list.accounts","actions":["invoke"]}
]
}决策输入(标记突出显示):
| 索赔 | 价值 |
|---|---|
| 澳元 | [MCP-A, MCP-B] |
| 工具权限 | (MCP-A, list.accounts) |
代理试图调用MCP-B的副作用工具:
{
"jsonrpc":"2.0",
"id":8,
"method":"tools/call",
"params":{
"name":"payments.transfer",
"arguments":{
"from_account":"ES00-1234",
"to_account":"ES99-9876",
"amount":"250.00"
}
}
}决策输入(请求要点):
| 字段 | 值 |
|---|---|
| params.name | payments.transfer |
| 目标资源 | https://mcp-b.example.com/mcp |
决定:
aud contains MCP-B: YES
tools bound to MCP-B: {}
requested tool: payments.transfer
result: DENY (insufficient_tool_scope)8.9示例I(否定):试剂重复使用 AT_mcp_multi 针对未列出的MCP服务器(受众不匹配)
Token只允许两个受众:
{
"iss":"https://as.example.com",
"sub":"agent_runtime",
"aud":[
"https://mcp-a.example.com/mcp",
"https://mcp-b.example.com/mcp"
],
"exp":1760669100
}代理调用中未列出的第三台MCP服务器 aud[]:
POST /mcp HTTP/1.1
Host: mcp-c.example.com
Authorization: Bearer
{ "jsonrpc":"2.0", "id":9, "method":"tools/call", "params":{ "name":"list.accounts" } }决策输入(亮点):
| 字段 | 值 |
|---|---|
| 预期资源 | https://mcp-c.example.com/mcp |
| 澳元 | [MCP-A, MCP-B] |
决定:
aud contains expected resource: NO
result: DENY (invalid_token / invalid_audience)9.海外建筑运营管理局应对令牌交换(RFC 8693)
代理不应直接针对MCP网关重用客户端的令牌。相反,代理执行 代表(OBO)的标准 流量使用 OAuth 2.0令牌交换 (RFC 8693):
AT_agent代表上游 主题 (后端客户端及其意图)。- 代理运行时是 男演员 执行交换(至少通过OAuth客户端身份验证标识,也可以通过
actor_token). - 已发行的代币
AT_mcp是(a) 观众导向 通过RFC 8707访问MCP资源resource以及(b) 缩小规模 为了达到目的,使用最小的工具集。
RFC 8693明确地对委托进行了建模。当颁发的令牌是JWT时,授权服务器可以使用 act 索赔(当前行为人)和/或 may_act (授权演员)。这使得审计具有确定性:下游网关可以区分“令牌是关于谁的”(sub)来自“谁现在在演戏”(act).
示例性令牌交换请求(代理作为机密客户端进行身份验证):
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
subject_token=&
resource=https://mcp-gw.example.com/mcp&
scope=list.accounts可选:使参与者明确(一些部署更喜欢这样,以获得更清晰的委托链):
...&
actor_token_type=urn:ietf:params:oauth:token-type:access_token&
actor_token=示例性已发布令牌形状(解码的JWT有效载荷;省略签名):
{
"iss": "https://as.example.com",
"sub": "client_backend_app",
"aud": "https://mcp-gw.example.com/mcp",
"exp": 1760669100,
"scope": "list.accounts",
"intent": "accounts.read",
"azp": "client_backend_app",
"act": { "sub": "agent_runtime", "typ": "service" }
}AS应执行的PDP规则(不可协商):
- 允许代理客户端使用令牌交换。
- 被请求
resource是该代理的批准下游资源。 - 请求的工具是主题令牌对意图和客户端角色所暗示的子集(单调下降).
- 授权被记录在所发放的令牌中(例如。,
act以及相关标识符,如jti/intent_id).
9.1单页令牌交换:对代理重要的参数
OAuth 2.0令牌交换(RFC 8693)通常被描述为“OBO”,但更精确地称之为 委派+降级:
- 代表团: 参与者(代理运行时)请求一个新的令牌来代表主体(后端客户端,或其他场景中的用户)行事。
- 应对措施: 新令牌有意地比输入上下文“更小”(资源/受众受限、寿命更短、作用域/工具更少)。
该请求是一个普通的OAuth令牌请求,具有特定的 grant_type:
grant_type=urn:ietf:params:oauth:grant-type:token-exchange您几乎总是在代理部署中使用的核心参数:
subject_token和subject_token_type:代表进行交换的一方的令牌(在本文中:AT_agent).resource(RFC 8707):您希望颁发的令牌对MCP保护资源有效的规范标识符。scope:请求的工具权限(或映射到结构化声明的粗略意图范围,具体取决于您的设计)。
在实际系统中变得有价值的可选参数:
actor_token和actor_token_type:显式地将代理运行时标识表示为代理方(一些as产品可以从客户端身份验证中推断出参与者,但显式的参与者令牌使链更难伪造)。requested_token_type:将输出固定到访问令牌(或urn:ietf:params:oauth:token-type:jwt当你想要一个可预测格式的JWT输出时)。audience(RFC 8693):可选参数,不等同于RFC 8707resource。一些产品将其视为目标客户端选择器或将其映射到aud;其他人忽略了它。对于MCP,更喜欢resource只要有可能;仅使用audience如果您的AS需要它,并且您有一个记录在案的映射aud.
对单个MCP资源和一个工具的最小请求:
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
subject_token=&
resource=https://mcp-a.example.com/mcp&
scope=list.accounts典型的响应(RFC 8693)返回新令牌 access_token 包括 issued_token_type:
{
"access_token": "",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 300,
"scope": "list.accounts"
}9.2两个身份,一条链:“主体”与“行为者”及其重要性
如果你跳过明确的委托语义,事件响应就会变成占星术。
Token Exchange为您提供了一个正式的模型:
- 主题: 令牌“关于”的标识(
sub在已发布的JWT中)。 - 男演员 当前使用委托权限的身份(
act当AS发出JWT时)。
这允许网关使用确定性声明来强制执行策略,并允许您进行审计:
- “client_backend_app导致转移”(主题)
- “agent_runtime执行了调用”(actor)
OBO链的ASCII跟踪:
+-------------------+ +---------------------+ +----------------------+
| backend client | | agent runtime | | Authorization Server |
| (confidential) | | (confidential) | | (PDP) |
+-------------------+ +---------------------+ +----------------------+
| | |
| 1) call agent with AT_agent | |
|------------------------------>| |
| | 2) token exchange (OBO) |
| |------------------------------->|
| | subject_token = AT_agent |
| | actor = agent client auth |
| | resource = MCP RS canonical |
| | scope/tools = downscoped |
| |
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
subject_token=&
resource=https://mcp-a.example.com/mcp&
scope=list.accounts payments.transfer预期AS结果(说明性):
{
"error": "invalid_scope",
"error_description": "Requested scope not permitted for subject token or client role"
}9.4多资源OBO:重复RFC 8707 resource 为什么 aud[] 出现
RFC 8707明确允许多次出现 resource 参数,但也警告说,具有多个受众的令牌具有更高的信任度/更大的爆炸半径,并不总是受到AS产品的支持。然而,在受控环境中,这是一种实用的优化:
- 单个业务事务可能需要两个MCP资源
- 反复呼叫
/token增加了延迟、负载和操作复杂性 - 短期多受众令牌可以保持交易单次通过
多资源令牌交换请求(同一事务,两个MCP服务器):
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
subject_token=&
resource=https://mcp-a.example.com/mcp&
resource=https://mcp-b.example.com/mcp&
scope=list.accounts payments.transfer如果AS选择铸造单个JWT访问令牌,它可能会将受众编码为数组:
{
"iss": "https://as.example.com",
"sub": "client_backend_app",
"aud": [
"https://mcp-a.example.com/mcp",
"https://mcp-b.example.com/mcp"
],
"exp": 1760669100,
"scope": "list.accounts payments.transfer",
"act": { "sub": "agent_runtime" }
}本文论述 aud[] 作为 有效但可选:它是控制AS和下游资源(或明确接受信任假设)的部署的性能和人体工程学杠杆。
执行要求不变:
- 每个PEP检查其自己的规范资源标识符是否存在于
aud - 并且所请求的工具被允许用于该资源(强烈建议使用资源限定权限)
9.5令牌内容:JWT访问令牌配置文件(RFC 9068)和委托声明
如果您发放JWT访问令牌,请与JWT访问代币配置文件(RFC 9068)保持一致,以便资源服务器能够一致地验证令牌,避免错误地接受ID令牌。
保持简单:
- 使用
typ标题值at+jwt(或application/at+jwt)并让资源服务器拒绝其他任何东西。 - 包括所需的索赔(
iss,sub,aud,exp).对待aud作为字符串或数组,并通过成员身份进行验证。 - 包含
scope(工具标识符),如果您需要确定性委托/审计,请包括act声明(RFC 8693)和相关声明,如jti. - 使用其中之一
client_id或azp(取决于您的生态系统)以保留哪个OAuth客户端获得了令牌。
解码的JWT标头(说明性):
{
"typ": "at+jwt",
"alg": "RS256",
"kid": "RjEwOwOA"
}MCP更完整的已发行代币草图(仅限有效载荷;说明性):
{
"iss": "https://as.example.com",
"sub": "client_backend_app",
"client_id": "agent_runtime_client",
"aud": "https://mcp-gw.example.com/mcp",
"exp": 1760669100,
"iat": 1760668800,
"jti": "8ddc2a5b-5e0f-4c2f-88f3-5d1a9b8f12a1",
"scope": "list.accounts",
"intent": "accounts.read",
"act": {
"sub": "agent_runtime",
"typ": "service"
},
"tool_permissions": [
{"rs":"https://mcp-gw.example.com/mcp","tool":"list.accounts","actions":["invoke"]}
]
}重要提示:不要治疗 scope 作为自由格式的字符串。按空格标记并进行精确比较。如果你把两者都包括在内 scope 以及结构化的权限声明(如 tool_permissions),选择一个作为真理的来源,并使另一个多余(或省略它)。
9.6代理系统中代币交换的强化检查表
当您为代理部署令牌交换时,假设一个充满敌意的环境并进行遏制设计:
- 短TTL
AT_mcp(分钟),并且仅在有界事务/会话内缓存。 - 除非您有单独的、风险可接受的管理路径,否则请拒绝工具的通配符范围。
- 考虑高影响工具的发送方约束令牌(mTLS或DPoP)。
- 要求精确
resource规范化和拒绝非规范形式。 - 排放相关性索赔(
jti,intent_id)并在网关和MCP服务器上记录它们。 - 决定是否允许多资源(
resource重复->aud[])并明确记录信任假设。
9.7示例7(拒绝):OBO/令牌交换中的范围升级尝试
安全故障模式是在令牌颁发期间拒绝授权服务器(PDP)的权限升级,而不是在工具调用到达网关之后。
场景:代理试图将主题令牌交换为下游MCP令牌,该令牌包含写入/破坏工具(payments.refund)这是主题背景和政策所不允许的。
顺序(代币发行提前被拒绝):
backend client -> agent runtime : call agent (AT_agent, intent=accounts.read)
agent runtime -> Authorization Server : token exchange
Authorization Server -> agent runtime : DENY (invalid_scope)令牌交换请求(表单参数;说明性):
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token_type=urn:ietf:params:oauth:token-type:access_token
subject_token=
resource=https://mcp-gw.example.com/mcp
scope=list.accounts payments.refund决策输入(请求要点):
| 字段 | 值 | 为什么重要 |
|---|---|---|
| grant_type | urn:ietf:params:oauth:grant-type:token-exchange | 委托/缩减流程(RFC 8693) |
| 主题标记 | `` | 代理可以请求的上限 |
| 资源 | https://mcp-gw.example.com/mcp | 规范目标MCP资源(RFC 8707) |
| 范围 | list.accounts payments.refund | 请求的工具集(包含升级) |
预期AS响应(说明性):
{
"error": "invalid_scope",
"error_description": "requested permissions exceed subject token and policy",
"reason": "downscope_violation",
"policy_version": "2026-02-17.1"
}决策输出(响应要点):
| 字段 | 值 | 为什么重要 |
|---|---|---|
| 错误 | invalid_scope | 清除OAuth失败面 |
| 原因 | downscope_violation | 审核/调试的确定性拒绝原因 |
| 策略版本 | 2026-02-17.1 | 使决策在各个部署中可重复 |
9.8示例8:多租户和命名空间实施
令牌包括租户和租户范围的工具:
请求:令牌包括租户和租户范围的工具
{
"tenant_id":"acme",
"tool_permissions":[{"tool":"acme.inventory.get","actions":["invoke"]}],
"aud":"https://mcp-gw.example.com"
}请求 globex.inventory.get 被拒绝 tenant_mismatch:
请求:MCP请求(跨租户工具)
POST /mcp HTTP/1.1
Host: mcp-gw.example.com
Authorization: Bearer AT_mcp
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"req-9.8-1",
"method":"tools/call",
"params":{
"name":"globex.inventory.get",
"arguments":{"sku":"X-42"}
}
}请求:MCP网关响应
{
"error":"access_denied",
"reason":"tenant_mismatch",
"token_tenant":"acme",
"requested_tool":"globex.inventory.get"
}10.候选授权服务器/IDP
Keycloak(主要开源候选)。Keycloak支持令牌交换,是构建具有明确受众纪律的单调下行策略的实用基线。
Microsoft Entra ID。Entra记录海外建筑运营管理局流程,广泛用于中期API委派。在实践中,当用令牌交换下游资源时,您必须严格控制断言受众和路由级受众固定。
Okta。Okta通过自定义授权服务器和服务应用程序交换控制记录了微服务链的OBO式委托。操作重点是明确范围的政策和交换同种异体。
网关上的工具级检查仅与生成令牌的策略一样好。
10.1维护规范的工具目录
最小字段数:
- tool_id(规范字符串。,
list.accounts) - 风险等级(读、写、管理、破坏性)
- 拥有团队/系统
- 哪个MCP网关/命名空间公开它
10.2角色到工具矩阵(示例)
ROLE ALLOWED TOOLS
------------------------ -----------------------------------------
mcp.accounts.reader list.accounts accounts.get accounts.search
mcp.payments.initiator payments.create payments.quote payments.status
mcp.payments.admin payments.refund payments.chargeback payments.cancel10.3代币交换治理(OBO)
你需要一个明确的政策,客户可以交换什么以及他们可以请求什么。推荐不变量:
tools(AT_mcp) 工具声明:
- 要么映射到 `scope` 条目(空格分隔的工具ID),或
- 映射到 `tool_permissions` 索赔。
限制代币交换:
- 只允许它用于代理运行时客户端
- 仅限于批准的资源
- 仅适用于批准的工具范围
### 10.5钥匙斗篷和MCP 2025-11-25:当前状态(26.4+/26.5.x)和应对模式
Keycloak是OAuth/OIDC部署的实用开源基线,从26.4+开始,它通过在所需的众所周知的位置发布OAuth 2.0服务器元数据并提供MCP设置指导,明确地将自己定位为MCP的授权服务器。
然而,MCP 2025-11-25提高了标准 *资源受限* 令牌和注册。当前状态最好描述为:
- **令牌交换(RFC 8693):** Keycloak 26.2+正式支持。这为您铸造缩减的下游代币提供了坚实的OBO基础。
- **MCP授权服务器元数据(RFC 8414):** 支持并记录,使MCP客户端能够基于标准发现Keycloak的OAuth端点。
- **资源指示器(RFC 8707):** 截至26.4/26.5.x,Keycloak中仍未实现,这意味着干净的“客户端发送 `resource=...` Keycloak将其映射到 `aud`“流量无法开箱即用。因此,Keycloak的MCP导向器将MCP 2025-11-25标记为部分支持。
- **客户端ID元数据文档(MCP 2025-11-25应该):** 尚未得到支持;根据部署限制,使用预注册或动态客户端注册(RFC 7591)。
#### 10.5.1应对模式:保持MCP叙述RFC 8707的整洁,但使用Keycloak兼容旋钮实施
避免设计偏差的最安全方法是保持 *概念性的* 模型一致性(RFC 8707 `resource` ->代币 `aud`),即使IdP无法处理 `resource` 本地还。
Keycloak的一种实用应对方法是:
1. **将每个MCP服务器/网关视为具有规范标识符的受保护资源** (您要显示的URL `aud`).
1. **使用Keycloak客户端作用域+受众映射器进行模型资源绑定**:
- 创建表示MCP资源类别或捆绑包的可选客户端作用域(例如: `mcp:tools`, `mcp:resources`, `mcp:prompts`).
- 将受众映射器附加到这些作用域,以便生成的访问令牌包含中的MCP服务器URL `aud`.
- 要求客户端在向该MCP服务器请求令牌时请求至少一个MCP作用域。
1. **强制网关中的一致性**:
- 将传入的主机/路径规范化为资源id。
- 验证这一点 `aud` 包含该id。
- 不要接受恰好拥有广泛受众的“通用”代币。
即使AS不接受,这也会为您提供确定性行为 `resource` 直接。
#### 10.5.2额外好处:受发送者限制的代币正在变得现实
Keycloak 26.4突出了对DPoP的全面支持。如果您的MCP客户端和网关能够处理它,那么发送方受限的访问令牌将显著降低高影响工具的重放值(仍然不能替代工具级授权)。
总体建议保持不变:将Keycloak用于其擅长的领域(OIDC、令牌交换、策略管理发行),并在网关(PEP)上实施协议感知强制,并为复杂的企业策略提供可选的外部PDP。
### 10.6治理:确定性授权需要一个目录
只有当您处理以下问题时,工具范围的授权才变得操作简单 **资源** 和 **工具** 作为一级受管对象,而不是代码中的特殊字符串。
一种保持决策确定性的实用治理模型:
1. **将MCP服务器/网关注册为IdP/as中的受保护资源**
- 选择规范资源标识符(通常是MCP服务器或网关路由的基本URL。, `https://mcp-gw.example.com/mcp`).
- 将该标识符视为必须出现在访问令牌中的值 `aud`.
1. **维护工具目录(按资源)**
- 完全合格的工具标识符(用于 `params.name`).
- 参数的JSON模式(PEP在执行前验证的内容)。
- 风险等级(读/写/不可逆)和任何升级要求。
- 可选:动作模型(`invoke`, `list`, `admin`)因此,令牌可以是操作范围的,而不仅仅是工具范围的。
1. **将角色/策略建模为(资源、工具、行动)的捆绑包**
- 角色被分配给OAuth客户端(后端应用程序、代理运行时),也可以选择分配给工作负载(SPIFFE身份、K8s SA等)。
- 令牌发行成为以下因素的确定性函数:(客户端身份、主题上下文、请求的意图、请求的资源、请求的工具)。
1. **在边缘保持简单的执法**
- 网关/PEP不“决定”;他们验证+匹配:
- JWT有效性+ `aud` 会员资格
- 精确的工具id匹配
- 可选动作匹配
- 可选的意图/会话绑定检查
此设置为您提供了“单一真实来源”属性:IdP/AS拥有权限模型,每个PEP都执行相同的合同。
一个最小的入职示例(新工具):
- MCP-B团队推出了一款新工具: `payments.refund` (不可逆转)。
- 安全登记簿 `payments.refund` 在资源的工具目录中 `https://mcp-b.example.com/mcp`,标记为高风险,需要HITL。
- 角色 `mcp.payments.refunder` 创建/更新以包括 `(MCP-B, payments.refund, invoke)`.
- 只有break glass工作流客户端才允许请求该角色(AS在令牌交换期间强制执行该角色)。
结果:网关逻辑保持不变;只有目录+AS策略会演变。
## 11.网关作为政治公众人物:为什么这通常是最好的选择
### 11.1好处
- 跨语言和团队的一致性。
- 在瓶颈处具有更好的可观察性和审计性。
- 更快推出新政策。
- 开发人员的潜在风险较小。
### 11.2权衡(ASCII表)
Trade-offs summary (compressed to avoid line overflow):
PEP in gateway (recommended) Pros: Central policy, consistent enforcement, strong audit point Cons: Extra component, must be highly available, policy rollout discipline
PEP in each MCP server Pros: Full semantic context, no extra hop Cons: Code duplication, drift risk, inconsistent logging/metrics
Scope string (tool names) Pros: Simple, widely compatible with OAuth tooling Cons: Expressiveness limits, scope explosion risk, parsing bugs if not careful
Structured claim (tool_permissions) Pros: Exact match, avoids scope parsing ambiguity, easy to extend per tool Cons: Custom claim, governance required, interop depends on clients
authorization_details (RAR) Pros: Structured and extensible constraints (amount limits, accounts, etc.) Cons: More complex, uneven ecosystem support
JWT local validation Pros: Low latency, no introspection dependency Cons: Revocation lag, key rotation and cache management
Introspection per call Pros: Immediate revocation, dynamic policy Cons: Latency, AS dependency, potential SPOF
## 12.Solo.io代理网关(agentgateway)作为实际实现
Solo.io发布了广泛的agentgateway材料,与本文很好地对应:
- MCP认证规范符合OAuth提供者和受保护的资源元数据公开(agentgateway可以充当验证JWT的资源服务器)。
- 使用细粒度RBAC策略的工具访问控制;文档和发行说明描述了策略驱动的授权,包括agentgateway中的Cedar策略引擎和工具访问指南中基于CEL的RBAC示例。
- 有状态JSON-RPC会话的协议感知路由,包括每个会话的授权考虑。
- MCP多路复用和工具联合(单个端点后面的多个MCP服务器,具有统一的工具列表)。
### 12.1为什么agentgateway与工具范围的授权相关
通用反向代理可以验证JWT,但它通常不能授权“此JSON-RPC消息是工具X的工具/调用”。agentgateway是MCP感知的,可以推理JSON-RPC主体、工具名称和会话行为。
它也很自然地适合作为 **代理网关(PEP#1)** 在代理API面前:同一个代理可以强制执行代理端点的JWT有效性和RFC8707受众锁定,同时仍然执行MCP路由的工具级授权(第5.1节,选项B)。
不修改MCP服务器代码即可执行不匹配场景:
- 代币许可证 `list.accounts`
- 有效载荷请求 `payments.transfer`
- 网关根据工具名称和声明拒绝
### 12.2 MCP认证和动态客户端注册
MCP授权指南强烈建议元数据发现和动态客户端注册,因为客户端事先不知道MCP服务器的集合。agentgateway文档提供了一个完整的演练,该演练使用Keycloak作为IdP,其中MCP检查器动态注册为客户端,执行OAuth,获取JWT,然后使用该JWT通过网关访问MCP工具。
简化视图:
(1) Client discovers metadata -> (2) Client registers -> (3) OAuth code flow -> (4) JWT to MCP
### 12.2.1直接映射到本文的代理网关配置模式
agentgateway公开了两个MCP特定的策略,它们几乎是为“应用程序代码外的PEP”量身定制的:
1. `mcpAuthentication`:使agentgateway在一个或多个MCP服务器前表现得像一个受OAuth保护的资源。它验证JWT,并可以公开受保护的资源元数据。
1. `mcpAuthorization`:在MCP方法调用和工具名称的上下文中评估CEL规则。至关重要的是,如果不允许使用某个工具,agentgateway会自动从列表响应中过滤它。
一个最小的(说明性的)独立配置如下:
Authentication: treat agentgateway as the resource server in front of MCP.
mcpAuthentication: issuer: https://idp.example.com/realms/mcp jwksUrl: https://idp.example.com/realms/mcp/protocol/openid-connect/certs provider: keycloak: {} resourceMetadata: resource: https://mcp-gw.example.com/mcp scopesSupported: - list.accounts - payments.transfer bearerMethodsSupported: - header
注意:agentgateway示例显示了其他承载方法,但MCP授权指南明确禁止通过URL查询参数发送访问令牌。对于MCP部署,首选 `Authorization: Bearer` 头球
授权规则(CEL)。文档显示了已关闭的规则 `mcp.tool.name` JWT声称:
mcpAuthorization: rules: # Anyone with a valid token can call list.accounts - 'mcp.tool.name == "list.accounts"' # Only a specific caller can call payments.transfer (example pattern) - 'jwt.sub == "payments-service" && mcp.tool.name == "payments.transfer"'
这还不是“动态范围成员”,但它展示了我们需要的两个核心机制:
- 刀具名称上的闸门(`mcp.tool.name`).
- 索赔门(`jwt.*`).
- 让网关执行强制和列表过滤。
对于可扩展的部署,您可以将第二条规则替换为对允许的工具集(例如结构化工具集)进行编码的声明的成员资格检查 `tool_permissions` 索赔,或标记化 `scope` 声称您的网关解析安全)。
### 12.2.2将复杂政治公众人物逻辑的授权委托给外部助手(ext_authz)
对于许多组织来说,让开发人员远离授权业务的最现实的方法是:
- 保持 **佩普** 在网关(靠近请求路径,协议感知,快速失败)
- 委托 **个人发展计划** 转到您拥有并可以独立版本的单独服务
Solo.io代理网关通过以下方式支持此模式 **外部授权(ext_authz)**从概念上讲,这是Envoy外部授权过滤器:网关向外部gRPC/HTTP服务发送授权CheckRequest;服务返回ALLOW/DENY;网关执行(返回403或向上游转发)。
从产品和治理的角度来看,这是一种有吸引力的关注点分离:安全团队可以拥有和版本化外部策略服务,而平台团队则保持网关配置的精简。特别是对于MCP,第一方确定性MCP authz模块(内置,但仍由策略驱动)将通过标准化资源规范化、工具匹配和错误映射进一步减少部署摩擦。
当CEL或内置RBAC不够时,这很有用,例如:
- RFC 8707资源的严格规范化规则(`aud` vs路由资源id)
- 用于声明匹配的工具,需要解析JSON-RPC有效载荷和跨联邦目标的映射工具
- 位于中央引擎中的企业策略(OPA、Cedar-as-a-service、自定义策略代码)
- “危险”工具的决策缓存、风险评分或升级身份验证要求
ASCII流(网关为PEP,助手为PDP):
+--------+ +----------------+ +--------------------+ +------------+ | Client | -----> | agentgateway | -----> | ext_authz helper | -----> | MCP server | |/Agent | | (PEP, protocol) | | (PDP, policy code) | | (tool impl)| +--------+ +----------------+ +--------------------+ +------------+ | | | | tools/call | CheckRequest | | Authorization: | (jwt + mcp context) | | Bearer AT_mcp |------------------------->| | | ALLOW / DENY | | |payload”实施可以实现,而无需在每个代理和工具服务器中嵌入授权逻辑。
12.2.2.1具体示例:助手看到了什么以及它如何否认工具不匹配
助手收到的简化(伪)CheckRequest(概念性的,而不是完整的原型):
CheckRequest
attributes:
request:
http:
method: "POST"
path: "/mcp"
headers:
authorization: "Bearer "
content-type: "application/json"
body: "{...jsonrpc payload...}"
metadata_context:
filter_metadata:
dev.agentgateway.jwt:
claims:
iss: "https://as.example.com"
sub: "client_backend_app"
aud: ["https://mcp-a.example.com/mcp","https://mcp-b.example.com/mcp"]
scope: "list.accounts payments.transfer"
tool_permissions:
- rs: "https://mcp-a.example.com/mcp"
tool: "list.accounts"
- rs: "https://mcp-b.example.com/mcp"
tool: "payments.transfer"助手提取:
resource_id从网关路由/上游选择(“本地”资源)tool_id来自MCP JSON-RPC(method == tools/call,params.name)
MCP-A应拒绝的MCP请求示例(该资源的工具错误):
{
"jsonrpc":"2.0",
"id":4401,
"method":"tools/call",
"params":{
"name":"payments.transfer",
"arguments":{"amount":"10.00","currency":"EUR"}
}
}确定性辅助决策:
resource_id = https://mcp-a.example.com/mcp
aud contains resource_id? YES
tool_id = payments.transfer
is tool permitted for rs=A? NO (only list.accounts is permitted)
=> DENY (403)最小拒绝响应模式(HTTP ext_authz风格)只是一个非2xx状态代码。对于gRPC ext_authz,助手返回一个DENIED响应,并且可以选择包含一个用于调试的主体。
关键点:这种拒绝发生在请求到达MCP服务器之前,因此即使工具服务器由不同的团队实现,您也可以获得一致的执行。
12.2.3外部认证助手作为治理压力阀
外部授权不仅是一种技术机制;这是一种操作控制:
- 您可以在不重新部署代理或MCP服务器的情况下发布策略更改
- 您可以集中审核决策(为什么被拒绝、缺少哪些声明、哪些资源不匹配)
- 您可以为特定工具添加速率限制或异常检测等防护措施
- 如果网关能够提供足够的上下文,则可以跨HTTP、MCP和其他协议运行单个PDP实现
12.3工具访问控制和发现减少
agentgateway工具访问指南描述了应用RBAC规则,以便根据JWT声明和工具上下文授权对MCP工具的访问。实际上,你想要:
- 否认未经授权
tools/call - 允许但过滤
tools/list因此只显示允许的工具
这与最小特权相一致:代理只看到他们被授权使用的工具,减少了混淆并限制了攻击面。
12.4 MCP复用:一个端点,多个MCP服务器
agentgateway支持在单个MCP连接后联合多个MCP服务器(目标)。网关可以聚合来自多个服务器的工具,并将其作为一个统一的工具服务器呈现,通常在工具名称前加上目标标识符。
ASCII多路草图:
Client connects to one endpoint: http(s)://agentgateway.example.com/mcp
+----------------------+ +---------------------+
| agentgateway |----->| MCP server A (time) |
| (single connection) |----->| MCP server B (fetch)|
| |----->| MCP server C (bank) |
+----------------------+ +---------------------+
Unified tool view (prefixed):
time_get_current_time
fetch_fetch
bank_list_accounts
bank_payments_transfer这对授权很重要,因为单个PEP可以在多个上游工具服务器之间实施一致的策略。
12.5组合起来:代理网关作为PEP#2
至少,配置:
- JWT验证(发行人、JWKS)
- 受众限制(以资源为中心
aud) - 工具授权策略
政策意图:
ALLOW if:
aud contains "https://mcp-gw.example.com/mcp"
AND method == "tools/call"
AND requested_tool in jwt.tool_permissions[].tool
Else DENY如果您的策略引擎是基于表达式的,并且您将权限存储在作用域中,请实现精确的标记化,而不是子字符串检查。
12.6优点总结
Agent Gateway (agentgateway) strengths as a PEP for MCP:
- Spec-aware MCP auth: implements MCP authentication/authorization patterns
- JWT validation at the edge: local verification against issuer/JWKS
- Authorization Server Proxy mode: proxy metadata/registration + IdP quirks
- Tool-level authorization: CEL rules using mcp.tool.name + jwt claims
- Automatic tools/list filtering: hides tools the caller cannot invoke
- Protocol-aware routing: can inspect JSON-RPC bodies to enforce policies
- Multiplexing/federation: one endpoint for multiple MCP servers; tools prefixed per target
- Session-aware policy: can vary discovery and access per session/client13.动手复制:MCP检查员作为测试工具
MCP检查器通常用于通过网关连接到MCP服务器。一个实用的测试配方:
- 使用流式HTTP通过MCP网关连接。
- 提供仅允许的不记名代币
list.accounts. - 验证:
- tools/list 仅返回 list.accounts (如果启用了过滤)。 - tools/call 为了 list.accounts 成功。 - tools/call 为了 payments.transfer 由于工具级授权错误而被拒绝。
这为您提供了一个用于策略更改和令牌发行逻辑的回归工具。
14.一致性套件(选定向量)
假设:
aud成员资格匹配(字符串或数组;资源必须存在)- 工具名称完全匹配
- 通配符已禁用
- 规范工具命名(建议使用小写)
Test vectors (compact, width-safe):
T01 ALLOW
aud ok: yes
token tools: {list.accounts}
requested: list.accounts
reason: tool in token
T02 ALLOW (with filtering)
aud ok: yes
token tools: {list.accounts}
requested: tools/list
reason: list is allowed, but response is filtered to permitted tools
T03 DENY
aud ok: yes
token tools: {list.accounts}
requested: payments.transfer
reason: mismatch (tool not permitted)
T04 DENY
aud ok: yes
token tools: {list.accounts}
requested: payments.payment
reason: mismatch (tool not permitted)
T05 DENY
aud ok: yes
token tools: {list.accounts}
requested: payments.transfer
reason: prompt-induced abuse blocked
T06 DENY
aud ok: NO
token tools: {list.accounts}
requested: list.accounts
reason: audience mismatch
T07 DENY (if strict tool IDs)
aud ok: yes
token tools: {list.accounts}
requested: LIST.ACCOUNTS
reason: case mismatch
T08 DENY
aud ok: yes
token tools: {list.accounts}
requested: list.accounts.v2
reason: version mismatch
T09 ALLOW
aud ok: yes
token tools: {list.accounts, accounts.get}
requested: accounts.get
reason: exact match
T10 DENY
aud ok: yes
token tools: {accounts.get}
requested: accounts.delete
reason: scope missing
T11 DENY
aud ok: yes
token tools: {list.accounts}
requested:
reason: malformed MCP request
T12 DENY
aud ok: yes
token tools: {list.accounts}
requested: tools/call without JWT
reason: authentication failure
T13 ALLOW (multi-aud)
aud ok: yes (aud contains target resource)
token tools: {(A,list.accounts),(B,payments.transfer)}
requested: list.accounts @ MCP-A
reason: permitted pair for resource
T14 DENY (multi-aud)
aud ok: yes (aud contains B)
token tools: {(A,list.accounts)}
requested: payments.transfer @ MCP-B
reason: tool not permitted for that resource
T15 DENY (multi-aud)
aud ok: NO (aud does not contain C)
token tools: {(A,list.accounts),(B,payments.transfer)}
requested: list.accounts @ MCP-C
reason: audience mismatch
T16 DENY (multi-aud)
aud ok: yes (aud contains B)
token tools: {(B,payments.transfer)}
requested: payments.payment @ MCP-B
reason: mismatch (tool not permitted)
T17 ALLOW (multi-aud, alias audiences for one gateway)
aud ok: yes (aud contains an alias of the same gateway)
token tools: {(GW,list.accounts)}
requested: list.accounts @ MCP-GW (alias host)
reason: alias mapped to canonical resource id before membership check
T18 ALLOW (multi-aud, trailing slash normalized)
aud ok: yes (aud contains /mcp/ but canonical is /mcp)
token tools: {(A,list.accounts)}
requested: list.accounts @ MCP-A
reason: canonicalization removes trailing slash before aud check
T19 DENY (multi-aud, wrong resource for a permitted tool)
aud ok: yes (aud contains A)
token tools: {(B,payments.transfer)}
requested: payments.transfer @ MCP-A
reason: tool permitted only for resource B
T20 DENY (multi-aud, flat tool list rejected)
aud ok: yes (aud contains A and B)
token tools: {list.accounts,payments.transfer} (no rs binding)
requested: payments.transfer @ MCP-A
reason: invalid scope contract for multi-aud tokens
T21 DENY (multi-aud, non-canonical rs value)
aud ok: yes
token tools: {(rs not canonical -> mismatch)}
requested: list.accounts @ MCP-A
reason: rs mismatch (require canonical rs in token)
T22 ALLOW (multi-aud, three resources)
aud ok: yes (aud contains C)
token tools: {(A,list.accounts),(B,payments.transfer),(C,fx.quote)}
requested: fx.quote @ MCP-C
reason: permitted pair for resource C
T23 DENY (multi-aud, structured claim beats scope string)
aud ok: yes
token tools: tool_permissions={(A,list.accounts)} but scope has transfer
requested: payments.transfer @ MCP-A
reason: tool_permissions is source of truth
T24 DENY (multi-aud, missing invoke action)
aud ok: yes
token tools: {(A,list.accounts)} but actions do not include invoke
requested: list.accounts @ MCP-A
reason: action not authorized
T25 ALLOW (multi-aud, tools/list filtered per resource)
aud ok: yes
token tools: {(A,list.accounts),(B,payments.transfer)}
requested: tools/list @ MCP-B
reason: allow+filter => only payments.transfer returned
T26 DENY (multi-aud, mixed-case tool name)
aud ok: yes
token tools: {(A,list.accounts)}
requested: LIST.ACCOUNTS @ MCP-A
reason: strict tool ids (case sensitive)ALLOW* 意味着:允许调用,但将响应过滤到允许的工具集。
15.安全考虑和强化
工具范围的授权是必要的,但还不够。在代理系统中,策略模型必须假设输入会影响工具选择,规划者可能会失败。目标是使系统在这些故障模式下安全。
15.1令牌处理和加密卫生
- 更喜欢工具执行的短期访问令牌(分钟,而不是小时)。仅在有界事务窗口内缓存。
- 对于高影响工具,考虑发送方约束令牌(mTLS或DPoP)以降低重放值。
- 遵循JWT最佳实践(RFC 8725):强制执行
iss,aud,exp,验证alg、pin接受的算法和句柄kid安全旋转。 - 使用
jti在适当的情况下进行重放检测,特别是对于写入工具。
15.2防止令牌传递并保留委托语义
一个反复出现的反模式是令牌传递:将调用者令牌转发到下游资源。相反:
- 代理应使用令牌交换(RFC 8693)或第一方客户端流获得为MCP资源明确铸造的下游令牌。
- 通过
act声明(主体vs参与者)或等效元数据,以便审计能够区分“谁造成的”和“谁执行的”。
15.3工具命名:规范化和易混淆防御
工具ID是一个授权界面。将工具名称视为标识符,而不是自由文本:
- 强制严格的ASCII字符集和长度限制。
- 使用规范形式(例如小写)并拒绝非规范变体。
- 如果您在任何地方接受非ASCII码,请在可能出现的地方规范Unicode(NFKC)并运行易混淆的检查。
15.4减小发现和爆炸半径
- 更倾向于允许+过滤
tools/list:只返回令牌允许的工具。 - 考虑工具风险等级,并要求对破坏性工具进行逐步控制(玻璃破碎角色、批准或人工确认)。
- 对每个工具(而不仅仅是每个客户端)实施速率限制和异常检测。
15.5映射到OWASP代理前10名(2026)
代理应用程序(ASI)的OWASP Top 10为健全性检查MCP部署提供了一个有用的分类法。本文中以令牌为中心的方法通过限制工具执行的爆炸半径直接减轻了几个ASI条目:
- ASI01代理目标劫持:严格的工具允许列表防止目标操纵变成任意行为。
- ASI02工具滥用和利用:即使规划失败,工具级授权也会阻止不安全的工具选择。
- ASI03身份和特权滥用:IdP中的角色映射到有界(资源、工具)权限;海外建筑运营管理局降低应对措施可防止特权升级。
- ASI06内存和上下文中毒:短暂的、意图范围的令牌限制了中毒上下文的持续时间。
- ASI08级联故障:单调的下行和每跳观众绑定会减少链放大。
将此映射视为检查表,而不是保证。授权是“权限层”;您仍然需要输入验证、输出处理和操作终止开关。
15.6实用的MCP服务器强化提醒
独立于令牌设计,MCP服务器和网关应:
- 任何远程服务器访问都需要身份验证。
- 在每次工具调用时验证令牌签名、受众和到期时间。
- 将策略执行集中在安全部门拥有的网关或运行时中(避免逐个开发人员实现)。
- 使用安全的错误处理:不要在错误响应中泄漏令牌、堆栈跟踪或工具内部。
附录C:测试向量(24例)
以下向量旨在针对工具范围的MCP网关+策略引擎(PEP/PDP拆分)执行。他们专注于规范化、受众绑定、重播属性、租户命名空间强制和交换单调性。
*示例角色到工具的映射。*
| 角色 | 工具ID |
|---|---|
| pricing_reader | 库存、报价、阅读 |
| order_operator | orders.create、orders.cancel、investory.set |
| 退款运营商 | 付款。退款,报价。阅读 |
| tenant_supply_ame | acme.inventory.get |
| tenant_supply_globex | globex.inventory.get |
| billing_exporter_v2 | billing.export.v2 |
| break_glass_ops | 付款、退款、订单、取消、账单、出口.v2(短TTL、批准) |
Test vectors (24 cases).
*测试向量(24例)。*
| 案例ID | 令牌aud | 令牌权限/范围摘要 | MCP方法+名称 | 规范(名称) | 预期 | 否认理由 |
|---|---|---|---|---|---|---|
| TV-01 | mcp gw | tp:investory.set,quote.read | call_tool inventory.get | inventory.get | 允许 | - |
| TV-02 | mcp gw | tp:investory.set,quote.read | call_tool payments.refund | payments.refund | 拒绝 | insufficient_tool_scope |
| TV-03 | 代理 | tp:investory.get | call_tool inventory.get | inventory.get | 拒绝 | invalid_audience |
| TV-04 | mcp gw | tp:investory.set | call_tool Inventory.Get | inventory.get | 拒绝 | non_canonical_tool_name |
| TV-05 | mcp gw | tp:investory.get | call_tool inventry.get | reject | 拒绝 | invalid_tool_name_charset |
| TV-06 | mcp gw | tp:investory.get;exp过去 | call_tool inventory.get | inventory.get | 拒绝 | token_expired |
| TV-07 | mcp gw | tp:investory.set;nbf未来 | call_tool inventory.get | inventory.get | 拒绝 | token_not_yet_valid |
| TV-08 | mcp-gw | 发行人不受信任 | call_tool inventory.get | inventory.get | 拒绝 | invalid_issuer |
| TV-09 | mcp-gw | 签名无效 | call_tool inventory.get | inventory.get | 拒绝 | invalid_token_signature |
| TV-10 | mcp gw | 无tp;范围包括库存集 | call_tool inventory.get | inventory.get | 允许 | - |
| TV-11 | mcp gw | tp:investory.set;无范围 | call_tool inventory.get | inventory.get | 允许 | - |
| TV-12 | mcp gw | tp+示波器报价。只读 | call_tool inventory.get | inventory.get | 拒绝 | insufficient_tool_scope |
| TV-13 | mcp gw | tenant acme;tp acme.inventory.get | call_tool acme.inventory.get | acme.inventory.get | 允许 | - |
| TV-14 | mcp gw | 租户acme;tp acme.inventory.get | call_tool globex.inventory.get | globex.inventory.get | 拒绝 | tenant_mismatch |
| TV-15 | mcp gw | tp库存集 | call_tool inventory.get(space) | inventory.get | 拒绝 | non_canonical_tool_name |
| TV-16 | mcp gw | tp库存集 | call_tool inventory/get | reject | 拒绝 | invalid_tool_name_charset |
| TV-17 | mcp gw | tp billing.legacy_export | call_tool billing.legacy_export | billing.legacy_export | 拒绝 | tool_deprecated |
| TV-18 | mcp gw | tp有效;policy_version太旧 | call_tool inventory.get | inventory.get | 拒绝 | policy_version_mismatch |
| TV-19 | 交换 | 主题为inventory.get;请求添加退款 | token_exchange | n/a | 拒绝 | downscope_violation |
| TV-20 | 交换 | 主题为盘点集;请求子集 | token_exchange | n/a | 允许 | - |
| TV-21 | mcp gw | tp quote.read;短TTL | call_tool quote.read | quote.read | 允许 | - |
| TV-22 | mcp gw | tp引用读取;长TTL切换策略 | call_tool quote.read | quote.read | 拒绝 | ttl_exceeds_policy |
| TV-23 | 交换 | 主题为盘点集;请求资源mcp-a、mcp-b;请求子集 | token_exchange | n/a | 允许 | - |
| TV-24 | mcp-a,b | aud\[\]:mcp-a、mcp-b;tp:investory.get | call_tool inventory.get | inventory.get | 允许 | - |
附录D:端到端流程(允许/拒绝)
本附录提供了具有明确跃点边界的端到端请求跟踪。其目的是可操作的:您应该能够将这些粘贴到测试线束中,并在AS(PDP)和MCP网关(PEP)上观察相同的允许/拒绝决定。
D.0代理网关拒绝:受众不匹配(令牌混淆)
这种故障模式在多跳系统中很常见:调用者获得了一个有效的令牌,但针对的是错误的资源。
步骤1:后端客户端意外地为MCP网关受众请求了一个令牌,但随后使用它来调用代理。
请求:客户端->AS:Client_credentials(错误的受众)
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=backend_app&
client_secret=...&
scope=agent.invoke inventory.get quote.read&
resource=https://mcp-gw.example.com响应:AS->客户端:AT_wrong(aud=mcp-gw)
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token":"AT_wrong",
"token_type":"Bearer",
"expires_in":300,
"scope":"agent.invoke inventory.get quote.read"
}步骤2:客户端使用AT_wrong调用代理网关。代理网关验证签名,但拒绝受众不匹配。
请求:客户端->代理GW:调用(错误的令牌受众)
POST /v1/agent/invoke HTTP/1.1
Host: agent-gw.example.com
Authorization: Bearer AT_wrong
Content-Type: application/json
{
"intent_id":"ord-2026-000124",
"input":"Check inventory for SKU X-42 and produce a quote."
}拒绝:代理GW->客户:无效_审核
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="audience mismatch", resource="https://agent-gw.example.com"
Content-Type: application/json
{
"error":"invalid_token",
"reason":"invalid_audience",
"expected_aud":"https://agent-gw.example.com",
"received_aud":["https://mcp-gw.example.com"]
}D.1允许:客户端到代理到令牌交换到MCP工具
步骤1:后端客户端获取代理令牌(AT_agent)。
请求:客户端->AS:Client_credentials(代理受众)
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=backend_app&
client_secret=...&
scope=agent.invoke inventory.get quote.read&
resource=https://agent-gw.example.com响应:AS->客户:AT_agent
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token":"AT_agent",
"token_type":"Bearer",
"expires_in":300,
"scope":"agent.invoke inventory.get quote.read"
}步骤2:客户端调用 代理网关(PEP#1),这将验证 AT_agent 并转发到代理运行时。
请求:客户端->代理GW:调用
POST /v1/agent/invoke HTTP/1.1
Host: agent-gw.example.com
Authorization: Bearer AT_agent
Content-Type: application/json
{
"intent_id":"ord-2026-000123",
"input":"Check inventory for SKU X-42 and produce a quote."
}步骤3:代理执行OBO令牌交换,以获得网关和工具范围内的MCP令牌(AT_MCP)。
请求:代理->AS:token_exchange(OBO下行范围)
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=AT_agent&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
resource=https://mcp-gw.example.com&
scope=mcp.call_tool inventory.get quote.read&
intent_id=ord-2026-000123响应:AS->代理:AT_mcp(缩减)
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token":"AT_mcp",
"token_type":"Bearer",
"expires_in":60,
"scope":"mcp.call_tool inventory.get quote.read",
"issued_token_type":"urn:ietf:params:oauth:token-type:access_token"
}步骤4:代理调用MCP网关,强制工具级授权。
请求:代理->MCP网关:工具/调用
POST /mcp HTTP/1.1
Host: mcp-gw.example.com
Authorization: Bearer AT_mcp
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"d1-1",
"method":"tools/call",
"params":{
"name":"inventory.get",
"arguments":{"sku":"X-42"}
}
}ALLOW:MCP网关->代理:允许
{
"jsonrpc":"2.0",
"id":"d1-1",
"result":{
"sku":"X-42",
"available":17,
"warehouse":"MAD-01"
}
}D.2拒绝AS:代币交换范围升级
代理请求的权限比主题令牌和策略中存在的权限多。
请求:代理->AS:token_exchange(尝试范围升级)
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=AT_agent&
resource=https://mcp-gw.example.com&
scope=mcp.call_tool inventory.get payments.refundDENY:AS->代理:invalid_scope
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error":"invalid_scope",
"error_description":"requested permissions exceed subject token and policy",
"reason":"downscope_violation",
"policy_version":"2026-02-17.1"
}D.3 MCP网关拒绝:租户命名空间不匹配
令牌对租户有效 acme,但该请求的目标是 globex 命名空间工具。
请求:代理->MCP网关:跨租户工具/调用
POST /mcp HTTP/1.1
Host: mcp-gw.example.com
Authorization: Bearer AT_mcp
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"d3-1",
"method":"tools/call",
"params":{
"name":"globex.inventory.get",
"arguments":{"sku":"X-42"}
}
}DENY:MCP网关->代理:tenant_mismatch
{
"error":"access_denied",
"reason":"tenant_mismatch",
"token_tenant":"acme",
"requested_tool":"globex.inventory.get"
}D.4 MCP网关拒绝:非规范工具名称(易混淆/变体)
请求使用非规范拼写工具。规范化在授权前被拒绝。
请求:代理->MCP网关:工具/调用(非规范)
POST /mcp HTTP/1.1
Host: mcp-gw.example.com
Authorization: Bearer AT_mcp
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"d4-1",
"method":"tools/call",
"params":{
"name":"Inventory.Get",
"arguments":{"sku":"X-42"}
}
}响应:MCP网关->代理:non_canonical_tool_name
{
"error":"access_denied",
"reason":"non_canonical_tool_name",
"canonical_name":"inventory.get",
"requested_name":"Inventory.Get"
}附录E:完整的交易跟踪(客户端->IdP->agent->MCP网关->MCP服务器)
本附录添加了与“IdP as PDP”框架对齐的具体痕迹。它们故意冗长,因此可以用作回归测试。
E.1 ALLOW:多资源交易 aud[] 资源合格的工具绑定
步骤0:后端客户端使用上游令牌调用代理 AT_agent (未显示;与附录D相同)。
第一步:代理交换 AT_agent 对于短期多资源MCP令牌,请求两个资源。
请求:代理->AS:token_exchange请求两个MCP资源
POST /oauth2/token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=AT_agent&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
resource=https://mcp-a.example.com/mcp&
resource=https://mcp-b.example.com/mcp&
scope=list.accounts payments.transfer&
intent_id=txn-2026-02-19-0001响应:AS->代理:AT_mcp_multi(JWT有效载荷草图)
{
"aud":[
"https://mcp-a.example.com/mcp",
"https://mcp-b.example.com/mcp"
],
"scope":"list.accounts payments.transfer",
"mcp_toolset":[
{"rs":"https://mcp-a.example.com/mcp","tools":["list.accounts"]},
{"rs":"https://mcp-b.example.com/mcp","tools":["payments.transfer"]}
],
"exp":1760669100,
"act":{"sub":"agent_runtime"}
}步骤2:代理使用以下命令调用MCP-A list.accounts 并且是允许的。
允许:代理->MCP-A:工具/调用(允许)
POST /mcp HTTP/1.1
Host: mcp-a.example.com
Authorization: Bearer AT_mcp_multi
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"e1-1",
"method":"tools/call",
"params":{"name":"list.accounts","arguments":{"limit":10}}
}步骤3:代理使用以下命令调用MCP-B payments.transfer 并且是允许的。
允许:代理->MCP-B:工具/调用(允许)
POST /mcp HTTP/1.1
Host: mcp-b.example.com
Authorization: Bearer AT_mcp_multi
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":"e1-2",
"method":"tools/call",
"params":{"name":"payments.transfer","arguments":{"amount":"10.00","currency":"EUR"}}
}E.2拒绝:相同的令牌,所选资源的工具错误
代理错误地发送 payments.transfer 到MCP-A。观众成员资格成功(因为MCP-A位于 aud[]),但工具绑定检查失败:
拒绝:MCP-A PEP:拒绝(MCP-A不允许使用该工具)
{
"error":"access_denied",
"reason":"insufficient_tool_scope",
"aud":"https://mcp-a.example.com/mcp",
"requested_tool":"payments.transfer",
"permitted_tools":["list.accounts"]
}E.3否认:受众与规范化的资源标识符不匹配
如果令牌 aud 包含 https://mcp-gw.example.com/mcp 但是请求被发送到 https://mcp-gw.example.com/mcp/ (尾随斜线),PEP必须在执行成员资格检查之前规范化请求资源ID。如果规范化应用不一致,您将通过别名看到虚假拒绝,或者更糟糕的是,虚假允许。
建议:为每个MCP保护的资源定义一个规范形式,并在IdP和网关中强制执行。
参考文献
附加安全指南:
- OWASP代理应用程序前10名(2026年):https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/
- 安全MCP服务器开发实用指南(v1.0):(本存储库中引用了本地副本)
标准和规范:
- 模型上下文协议(MCP)规范(2025-11-25):https://modelcontextprotocol.io/specification/2025-11-25
- RFC 6749:OAuth 2.0授权框架:https://www.rfc-editor.org/rfc/rfc6749
- RFC 6750:Auth2.0授权框架:承载令牌使用:https://www.rfc-editor.org/rfc/rfc6750
- RFC 7519:JSON Web令牌(JWT):https://www.rfc-editor.org/rfc/rfc7519
- RFC 8707:OAuth 2.0的资源指标:https://www.rfc-editor.org/rfc/rfc8707
- RFC 8693:OAuth 2.0令牌交换:https://www.rfc-editor.org/rfc/rfc8693
- RFC 9068:OAuth 2.0访问令牌的JWT配置文件:https://www.rfc-editor.org/rfc/rfc9068
- RFC 9396:OAuth 2.0丰富授权请求:https://www.rfc-editor.org/rfc/rfc9396
- RFC 9728:OAuth 2.0受保护的资源元数据:https://www.rfc-editor.org/rfc/rfc9728
- RFC 8414:OAuth 2.0授权服务器元数据:https://www.rfc-editor.org/rfc/rfc8414
- RFC 8725:JSON Web令牌最佳实践:https://www.rfc-editor.org/rfc/rfc8725
网关模式(代理网关/Solo/Envoy外部身份验证):
- agentgateway概述和文档:https://agentgateway.dev/docs/kubernetes/latest/
- 代理网关:MCP连接:https://agentgateway.dev/docs/mcp/connect/
- 代理网关:MCP身份验证:https://agentgateway.dev/docs/mcp/mcp-authn/
- 代理网关:MCP授权(mcpAuthority):https://agentgateway.dev/docs/mcp/mcp-authz/
- agentgateway:外部授权(extAuthz):https://agentgateway.dev/docs/configuration/security/external-authz/
- Envoy:外部授权过滤器(ext_authz):https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter
- Solo.io文档:安全访问MCP服务器:https://docs.solo.io/agentgateway/2.1.x/mcp/mcp-access/
- Solo.io文档:控制对工具的访问:https://docs.solo.io/agentgateway/2.1.x/mcp/tool-access/
- Solo.io文档:BYO外部认证服务(Envoy ext_authz proto):https://docs.solo.io/gateway/2.0.x/security/extauth/byo-ext-auth-service/
- Solo.io MCP学院实验室(多路复用+授权政策):https://www.solo.io/mcp-academy/multiplex-mcp-servers-auth-kgateway-agentgateway
- Solo.io博客:大修支持A2A、MCP和Kubernetes网关API的代理网关:https://www.solo.io/blog/updated-a2a-and-mcp-gateway
- Solo.io博客:为什么我们需要一个新的AI代理网关:https://www.solo.io/blog/why-do-we-need-a-new-gateway-for-ai-agents
Keycloak(开源IdP/AS候选和令牌交换支持):
- Keycloak指南:与模型上下文协议(MCP)集成:https://www.keycloak.org/guides/securing-apps/mcp-authz-server
- Keycloak博客:Keycloak 26.2(RFC 8693)正式支持标准令牌交换:https://www.keycloak.org/2025/05/standard-token-exchange-kc-26-2
- Keycloak博客:Keycloak 26.4.0发布(DPoP、MCP AS元数据;RFC 8707尚未实现):https://www.keycloak.org/2025/09/keycloak-2640-released
- 钥匙斗篷释放页面(26.5.3):https://github.com/keycloak/keycloak/releases/tag/26.5.3
