企业级 MCP 授权架构:一份多租户参考设计
Model Context Protocol 规范终于对授权说了点实在话:使用 OAuth 2.1,用 resource indicators 把每个令牌绑定到特定受众,绝不把客户端令牌透传给下游 API。这些要求是真的。问题在于,它们散落在核心规范、一份安全最佳实践文档和半打 IETF RFC 之间,而几乎每一个为这些关键词排名的页面,都是止步于单服务器场景的安全厂商清单。
这是我们的参考设计,适用于当客户需要一台 MCP 服务器:服务多个租户、对接企业身份提供方、并能通过采购方的安全审查。它厂商中立,锚定在规范 2025-11-25 修订版,并画出了别人都没画的那张图:从身份提供方到按租户隔离数据面的完整信任边界。内容源自 Wavect 在 AI 产品与安全加固上的工作。
在设计一台企业级 MCP 服务器?
联系 Wavect各司其职:MCP 服务器是资源服务器,不是授权服务器
最常见的错误,是把 MCP 服务器建成它自己的登录系统。它不是。按当前规范,MCP 服务器是一台 OAuth 2.1 资源服务器。它的职责是校验令牌,而不是签发令牌。签发由一台独立的授权服务器负责,在企业里就是你的身份提供方(Entra ID、Okta、Auth0、Keycloak、Ping),由它与用户交互并签发访问令牌。
这个拆分是近期才有的。第一版授权规范(2025-03-26)期望 MCP 服务器同时充当授权服务器和资源服务器。2025-06-18 修订版通过 pull request 284 拆分了这两个角色,正是这个拆分才让企业单点登录成为可能。授权服务器仍然可以与资源服务器同处一地,但 MCP 服务器被规定的职责就是校验令牌。把这个角色模型定对,下游的一切都会各就各位。定错了,你就在拙劣地重造身份系统。
当前规范究竟要求什么?
表格之前先说两点。第一,MCP 里的授权是可选的,且只为 HTTP 传输定义。位于 STDIO 传输之后的服务器根本不该遵循这份规范;它从环境中取凭据。第二,OAuth 2.1 仍是 IETF 草案,不是已发布的 RFC,所以别给它引用 RFC 编号。下面是 2025-11-25 修订版的标准面。
| 标准 | 引用 | 在 MCP 中的角色 | 级别 |
|---|---|---|---|
| OAuth 2.1 | draft-ietf-oauth-v2-1 | 核心授权框架 | 授权服务器 MUST |
| Protected Resource Metadata | RFC 9728 | 服务器把客户端指向其授权服务器 | 资源服务器 MUST |
| Resource Indicators | RFC 8707 | 把令牌绑定到一个受众 | 客户端 MUST |
| Authorization Server Metadata | RFC 8414 | 授权服务器发现 | MUST(此项或 OIDC) |
| OpenID Connect Discovery | OIDC Core 1.0 | 授权服务器发现的替代方式 | MUST(此项或 8414) |
| PKCE | OAuth 2.1 第 7.5.2 节 | 保护 authorization code | 客户端 MUST,用 S256 |
| Client ID Metadata Documents | draft-ietf-oauth-client-id-metadata-document | 用 URL 作为 client_id | SHOULD(2025-11-25 新增) |
| Dynamic Client Registration | RFC 7591 | 客户端自动注册 | MAY(原为 SHOULD) |
| Bearer 令牌用法 | RFC 6750 | challenge 与 insufficient_scope | 被引用 |
注意两处让人栽跟头的版本差异。2025-11-25 修订版把 Dynamic Client Registration 从 SHOULD 降为 MAY,并新增了强制的 PKCE S256 方法,外加要求客户端在开始前先验证 PKCE 支持。如果授权服务器 metadata 没有声明 code_challenge_methods_supported,合规的客户端必须拒绝开始该流程。
授权流程从头到尾怎么走?
整个握手就是一个发现步骤,后接一个标准的 authorization code flow。客户端不带令牌访问服务器,被告知去哪里认证,完成认证,然后带着一个为这台特定服务器签发的令牌回来。
那张图里藏着两个必做细节。在 challenge 时,服务器必须按 RFC 9728 返回 401 Unauthorized,并带一个携带 resource_metadata URL 的 WWW-Authenticate 头。2025-11-25 修订版也允许客户端直接探测 well-known URI。在 authorization 和 token 请求里,客户端必须发送一个设为 MCP 服务器规范 URI 的 resource 参数,而且无论授权服务器是否声称支持它,都要发送。
为什么禁止令牌透传,以及你如何校验受众?
这是规范的安全核心,也是大多数自建服务器翻车的地方。规则很直白:MCP 服务器必须校验每个访问令牌都是把它当作目标受众而签发的,并拒绝其他一切。它不得接受为另一个服务签发的令牌,也不得把收到的令牌转发给下游 API。最后这个反模式就是令牌透传,规范对它明令禁止。
原因是 confused deputy 问题。如果你的服务器接受并转发它没有校验的令牌,那么为某个服务泄露或签发的令牌,就变成了另一个服务的万能钥匙,基于受众的限流被绕过,下游审计轨迹显示的是错误的身份。校验受众只是一次 claim 检查,而它就是资源服务器与法律责任之间的那条线。
on every tool call:
token = bearer_from_authorization_header() # never from a session
claims = verify_signature(token, jwks_of(trusted_issuer))
assert claims.iss == expected_issuer # pinned per tenant
assert this_server_uri in claims.aud # RFC 8707 audience
assert not expired(claims) and not before(claims)
assert required_scope_for(tool) in claims.scope
tenant = claims["tenant"] or claims["org_id"]
enforce_tenant(tenant) # every query, every secret
注意这里没有什么:没有用会话来做认证的查找。规范明确要求服务器校验每一个进入的请求,且不得用会话来做认证。会话标识符,如果你要用的话,必须是非确定性的,并绑定到用户身份,这样猜出来的标识符就无法冒充另一个租户。
服务器如何在不泄露令牌的情况下访问下游 API?
如果 MCP 服务器需要调用下游 API,它就作为该 API 的 OAuth 客户端,使用一个由上游授权服务器签发的独立令牌。规范告诉你不该做什么(不要透传进入的令牌),但把怎么做留给了生态。主流做法是 RFC 8693 定义的令牌交换:服务器把受众为 MCP 服务器的进入令牌,换成一个受众为下游 API 的新令牌。
服务器在这里扮演双重角色:对客户端是资源服务器,对 API 是 OAuth 客户端。优先选择委托而非冒充,这样原始用户留在 sub claim 里,行动链记录在 act claim 里,正是这一点让你的审计轨迹在跨越这一跳后仍然诚实。令牌交换与 on-behalf-of 流程都在 MCP 核心规范之外,所以把它们当作身份提供方或网关的职责,而不是 MCP 的要求。
多租户参考架构长什么样?
这就是拓扑。一个身份提供方签发绑定受众的令牌,每个租户一个 realm。客户端把这些令牌出示给企业信任边界内一台共享的资源服务器或网关。服务器校验受众、执行按工具的 scope、锁定租户 realm,然后才触达工具、数据和下游 API,每一项都按租户隔离。
大多数多租户文章的错误,是把隔离当成数据库问题。它不是。多租户必须在每一层执行,而租户身份来自经过验证的令牌 claim,绝不来自模型能影响的工具输入。下面这张表就是我们逐层走的清单。
| 层 | 隔离控制 | 省略后的后果 |
|---|---|---|
| 身份 | 按租户锁定 issuer 与 realm;拒绝其他 realm 的令牌 | 租户 A 通过租户 B 的 realm 登录 |
| 令牌 | 每个请求都校验受众(RFC 8707)与过期 | 在别处签发的令牌被接受 |
| Scope | 从 IdP 组和角色映射出的最小权限 scope | 只读角色执行了写操作 |
| 工具 | 按租户层级与 scope 过滤可见工具 | 智能体发现一个它无法安全使用的工具 |
| 凭据 | 按租户的密钥放在保险库里,对模型和客户端不透明 | 一个租户的 API key 服务了另一个租户 |
| 数据 | 基于租户 claim 的行级安全 | 跨租户读取行数据 |
| 审计 | 按租户、不可变、带 correlation ID 的日志 | 事故期间无法归因 |

"租户身份是一个经过验证的令牌 claim。一旦它来自模型可以写入的工具参数,你的隔离就只是演戏。"
按工具的 scope 与 step-up 授权如何运作?
核心协议在 OAuth 层处理 scope,而不是按单个工具,所以真正的按工具授权要你自己在上面搭。规范给了你原语。从最小开始,避免像 all 或 full-access 这样的大杂烩 scope。当某个工具需要的超出当前令牌所授予的,2025-11-25 修订版定义了一个干净的 step-up:服务器返回 403,带 WWW-Authenticate: Bearer error="insufficient_scope" 和所需 scope,客户端只为该 scope 做一次增量授权,然后重试。
实践中我们把身份提供方的组和 SCIM 角色映射到 scope,再让每个工具以一个所需 scope 为门槛。分析师角色带 invoices:read,能看到只读工具;它永远不带 ledger:write。这与我们在授权审查里用的是同一套纪律,也正是采购团队真正会去测的东西。
如何为 MCP 服务器加上企业 SSO?
有两种形态,选择决定了你大部分的运营成本。
第一种是每台服务器各自 OAuth:每台 MCP 服务器把它的 authorization_servers metadata 指向企业身份提供方。对一两台服务器很干净,到了机队规模就很重复。第二种是 MCP 网关:在所有服务器前放一个执行策略的统一入口,集中做发现、令牌校验、scope 映射、令牌交换和审计。对于跨多个租户运行许多服务器的企业,网关通常是正确答案,因为你有一个地方执行隔离,也有一个地方做审计。
关于协议:OIDC 在 2025-11-25 修订版里是一等公民,它要求授权服务器支持 RFC 8414 metadata 或 OpenID Connect Discovery,并要求客户端两者都尝试。MCP 没有原生 SAML。纯 SAML 的企业要通过其身份提供方,或通过一个为 MCP 服务器暴露 OAuth 或 OIDC 前端的 broker 来搭桥。
如何治理客户端注册与凭据生命周期?
Dynamic Client Registration 是早期部署的软肋,因为它让任意客户端都能注册进来。2025-11-25 修订版把它降为 MAY,转而偏好一个三层模型:已知客户端用预注册凭据,然后是 Client ID Metadata Documents(客户端用一个 HTTPS URL 作为 client_id),再然后 DCR 作为兜底。如果你允许后两者中的任何一种,请把 issuer 加入 allowlist,并留意规范点名的 SSRF 与 localhost 重定向风险。企业应当把注册绑定到某个租户,并让未知客户端走一条管理员审核队列。
凭据生命周期遵循 OAuth 2.1:签发短寿命访问令牌,为公共客户端轮换 refresh 令牌,安全存储令牌,别把令牌放进 URL query string,并在每个请求都发送 bearer 令牌。我们作为起点使用的具体默认值:
| 凭据 | 典型寿命 | 轮换 |
|---|---|---|
| 访问令牌(客户端到服务器) | 5 到 15 分钟 | 用 refresh 令牌重新签发 |
| Refresh 令牌(公共客户端) | 数小时到数天,滑动 | 每次使用都轮换 |
| 下游令牌(RFC 8693) | 数分钟,按调用或按会话 | 重新交换,不要大范围缓存 |
| 保险库中的按租户密钥 | 长,但可吊销 | 定期加上疑似泄露时 |
你必须捕获哪些审计事件?
核心规范没有专门的审计日志要求,但令牌透传的分析已经替你把理由说清了:透传会破坏审计轨迹,因为下游日志显示的是错误身份。在多租户系统里,审计就是你证明隔离生效的方式。把下面这些按租户记录,并带一个能挺过下游那一跳的 correlation ID。
- 令牌校验结果:接受、因受众拒绝、因 issuer 拒绝、已过期。
- 授权决策:调用了哪个工具、所需 scope、授予的 scope、放行或拒绝。
- Step-up 事件:请求的 scope 与实际授予的子集。
- 令牌交换:为哪个 subject 和 actor 签发了哪个下游受众。
- 租户上下文:每条记录都带经过验证的租户 claim,好让一次查询能归因到一个租户、一个用户和一个工具。
按服务器、网关,还是托管身份:你怎么选?
给三种可行拓扑一张速用决策矩阵。
| 方式 | 最适合 | 成本 |
|---|---|---|
| 每台服务器各自 OAuth | 一两台服务器、单租户 | 搭建成本低,规模上表现差 |
| MCP 网关 | 许多服务器或许多租户 | 搭建成本较高,只有一个执行点 |
| 托管 IdP 集成 | 你本来就在 Entra 或 Okta 里 | 厂商锁定,合规最快 |
只要你排好顺序,从单租户服务器走到多租户并不是重写:先引入租户 claim 并锁定 issuer,把密钥搬进按租户的保险库,加上基于该 claim 的行级安全,再过滤工具可见性并打开按租户审计。每一步都能独立交付、独立测试。用对抗方式测试隔离:拿租户 A 的令牌去打租户 B 的数据,确认它在令牌层就被拒绝,而不仅仅是在查询层被过滤。
最终思考
MCP 授权规范给你三条硬性要求:以 OAuth 2.1 为框架、用 resource indicators 绑定受众的令牌、以及禁止令牌透传。其余对企业重要的一切,多租户隔离、按工具的 scope、委托的下游访问与审计,都在规范之上,由你来设计。把 MCP 服务器当成一台负责校验与执行的资源服务器,把租户身份放在一个经过验证的 claim 里,并记录足够多的信息来证明隔离生效。做到这些,MCP 服务器就不再是你智能体栈里最薄弱的一环。
来源与延伸阅读
- Model Context Protocol, Authorization specification (2025-11-25 revision)
- Model Context Protocol, Authorization specification (2025-06-18 revision)
- Model Context Protocol, Security Best Practices (token passthrough, confused deputy)
- MCP pull request 284, separating the resource server and authorization server roles
- RFC 9728, OAuth 2.0 Protected Resource Metadata
- RFC 8707, Resource Indicators for OAuth 2.0
- RFC 8414, OAuth 2.0 Authorization Server Metadata
- RFC 7591, OAuth 2.0 Dynamic Client Registration
- RFC 8693, OAuth 2.0 Token Exchange
- RFC 9068, JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens
- RFC 6750, OAuth 2.0 Bearer Token Usage
- OAuth 2.1 Authorization Framework (IETF draft)