一个内部 MCP 服务器,能查订单、能改库存、能发邮件。开发者说"我们接了 OAuth"。上线三个月后复盘,发现三件事:客户端送来的 token 被服务器原样转发给了下游 API;授权发现流程里的元数据 URL 直接取自服务器响应,没人校验它是不是指向了内网;工具描述在某次版本更新后悄悄多了一句话,而审批记录里只有最初那一版。
三件事,没有一件是"密码被破解"。它们是同一个问题的不同切面:在 MCP 规范里,授权(Authorization)是 OPTIONAL 的。协议不保证你的调用经过认证,它只规定——如果你决定要做,必须怎么做。
这句话值得每个接 MCP 的团队抄在评审表第一行。
很多人一上来就问"MCP 怎么登录",这个问题本身就偏了。MCP 授权规范定义的是传输层授权:MCP 客户端代表资源所有者,向受限的 MCP 服务器发起请求。这里没有用户会话,只有三方的身份关系:
这个分工是 2025 年 6 月版规范定下的,也是最容易被忽略的一条架构建议:把授权服务器从 MCP 服务器里拆出去。工具代码会频繁迭代,身份体系不该跟着一起重新部署。
还有一条边界必须说清:规范只对 HTTP 类传输适用(SHOULD 遵循);本地 stdio 传输 SHOULD NOT 走这套流程,凭据从环境变量读取。也就是说,"本地跑一个 MCP 服务器"和"远程连一个 MCP 服务器",是两种完全不同的安全模型,用同一份检查单去审,一定会漏。
以 2026-07-28 版规范为准,第一次连接一个受保护的 MCP 服务器,应当发生这些事:
第一步,被拒绝。 客户端不带 token 调用,服务器返回 401,并在 WWW-Authenticate 里给出元数据地址和本次操作所需的 scope:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"那个 scope 是 SHOULD,但极其关键:它让客户端第一次就申请对的权限,而不是把全部权限一次性要回来。
第二步,读受保护资源元数据(RFC 9728)。 这是 MCP 服务器的 MUST。文档至少要列出 authorization_servers,客户端 MUST 用它来发现授权服务器。
第三步,发现授权服务器(RFC 8414 / OIDC Discovery)。 授权服务器 MUST 至少提供一种元数据发现机制,而客户端 MUST 两种都支持。返回值里的 issuer 必须与构造 URL 时用的 issuer 完全一致,不一致就丢弃。
第四步,拿到 client_id。 2026-07-28 版把优先级排清楚了:预注册 > Client ID Metadata Documents(CIMD)> 动态客户端注册(DCR)。DCR(RFC 7591)已被标记为 deprecated,仅为兼容保留。CIMD 的做法很优雅:client_id 本身就是一个 HTTPS URL,授权服务器去取一份 JSON 元数据并校验 redirect_uris,不需要为每个客户端保存注册记录。
第五步,授权码 + PKCE + resource。 客户端 MUST 实现 RFC 8707,在授权请求和令牌请求里都带上 resource 参数,值是 MCP 服务器的 canonical URI。这个参数不是装饰:它把 token 的受众(audience)钉死在这台服务器上。此外,客户端 MUST 按 RFC 9207 校验授权响应里的 iss,与之前记录的 issuer 做字符串比较;不匹配就拒绝兑换授权码,连错误信息都不能采信。
第六步,每次请求都带 token。 客户端 MUST 用 Authorization: Bearer 头,且每一个 HTTP 请求都要带;token MUST NOT 出现在 URL query 里。服务器 MUST 校验 token 是专门签发给自己的,无效或过期 MUST 返回 401。
六个 RFC 编号,值得贴在工位上 RFC 6750(Bearer Token)· RFC 8414(AS 元数据)· RFC 7591(DCR,已弃用)· RFC 8707(Resource Indicators,受众绑定)· RFC 9728(受保护资源元数据)· RFC 9207(iss 校验)
读到这里可以发现一条主线:规范花了大量篇幅,不是为了让"能连上",而是为了让每一张 token 都只在一个地方有效。
如果你的实现是对着 2025-06-18 或 2025-11-25 版写的,有四处需要动:
变化 | 要求 | 谁要改 |
|---|---|---|
DCR 弃用,CIMD 上位 | SHOULD 支持 CIMD;仍用 DCR 的客户端 MUST 带 | 授权服务器 + 客户端 |
授权响应校验 | 客户端 MUST 把响应中的 | 客户端 |
凭据按 issuer 绑定 | 客户端 MUST 以授权服务器 issuer 为键存储凭据,MUST NOT 跨 issuer 复用,AS 变更时 MUST 重新注册 | 客户端 |
协议无状态化 | 移除 | 双方 |
最后一条尤其值得注意。最新版安全最佳实践里,原来的 "Session Hijacking" 变成了 State Handle Hijacking,强制性条款也改成两句更干脆的话:实现授权的 MCP 服务器 MUST 校验所有入站请求;MUST NOT 把持有 state handle 当作身份认证。
一句话概括这版修订的方向:身份不再依附于任何连接状态,每一次请求都要自己证明自己。
MCP 服务器收到客户端的 token,不校验是不是发给自己的,转手拿去调下游 API。代码能跑,功能正常,审计链条却断了:下游看到的是"某客户端"的凭据,而不是服务器自己的身份;一旦出事,无法追溯是谁授权的。
规范的措辞没有任何模糊空间:MCP 服务器 MUST NOT 接受任何不是明确签发给自己的 token,授权规范中"明确禁止"透传。要调下游服务,服务器应当作为独立客户端去获取自己的 token,或走 token exchange。
这是代理型 MCP 服务器的经典坑。四个条件同时成立就会出事:代理对第三方授权服务器使用静态 client_id;允许 MCP 客户端动态注册;第三方在首次授权后种下 consent cookie;代理在转发前没有做逐客户端同意。攻击者构造一个带恶意 redirect_uri 的链接,浏览器带着旧的同意 cookie 跳过同意页,授权码就落到了攻击者手里。
官方安全最佳实践给出的 MUST 密集到近乎琐碎,但每一条都对应一个真实绕过:
client_id 注册表,并在发起第三方授权流程之前核查;redirect_uri,并做 CSRF 防护与防 iframing;redirect_uri MUST 精确字符串匹配注册值,变更后未重新注册即拒绝;state MUST 密码学随机、单次使用、短过期(如 10 分钟),且含 state 的 cookie/session MUST NOT 在用户批准同意页之前设置——这一条时序要求最常被写反。resource_metadata、authorization_servers、token_endpoint 这些 URL 全部来自服务器响应。恶意服务器把它们指向 169.254.169.254 或内网地址,客户端就会替攻击者去访问云元数据服务。服务端部署的 MCP 客户端 MUST 评估并实施 SSRF 缓解:强制 HTTPS、屏蔽私网与保留地址段、校验重定向每一跳、必要时走出口代理。
同一份文档里还有几条"看起来是前端问题、其实是 RCE"的要求:授权 URL MUST 只允许 http/https,生产 MUST 用 https,MUST 拒绝 javascript:、data:、file: 等 scheme;MUST NOT 用 shell 命令打开 URL;支持一键配置本地 MCP 服务器的客户端,MUST 在执行前完整展示命令、明确提示危险、获得显式批准并允许取消。
一个容易被误引的事实:MCP 官方 Security Best Practices 并没有覆盖工具投毒(tool poisoning)、rug pull 或独立的 prompt injection 章节。它的强制条款几乎全部集中在 OAuth 边界、同意流程、SSRF、本地命令执行与 URL 校验上。把这些攻击写成"规范要求 MUST 怎么做",是二手资料里很常见的错误。
补位的是 OWASP 的 MCP Top 10:Token 管理失当与秘密泄露(MCP01)、认证与授权不足(MCP07)在列,工具投毒(MCP03)、上下文载荷提示注入(MCP06)、影子 MCP 服务器(MCP09)等同样在列。换句话说:规范管的是"谁能调",OWASP 管的是"调用里藏了什么",两套清单要叠着用。
现实已经给出足够多的注脚。两个 2026 年的官方 CVE 记录,都是"没认证"直接造成的:
@zereight/mcp-gitlab 2.1.27 之前,SSE 模式下所有 MCP 工具无认证暴露;upload_markdown 的 file_path 未净化,攻击者可读 /proc/self/environ 窃取 GITLAB_PERSONAL_ACCESS_TOKEN,进而接管整个 GitLab 账号。这是 Docker 部署的默认配置。app.listen(PORT) 未指定 host 导致绑定所有网卡,文档管理 API 无认证——列举、读取、插入、删除文档,等于可以随意篡改智能体的知识库。CWE 编号正是 306:关键功能缺失认证。再看几组第三方扫描数字,口径各不相同,但方向一致:有安全媒体汇总称,截至 2026 年 2 月,官方 MCP Registry 抽查的 518 个服务器中约 41% 零认证;一份覆盖约 2 万台服务器的调查里,采用 OAuth 的仅 8.5%,53% 仍依赖静态 API Key;另有厂商报告发现 492 台 MCP 服务器直接暴露在公网且无需认证。这些数字不能相加,也不必当作精确值——它们共同说明的是:认证缺席不是个别疏忽,而曾经是生态的默认状态。
服务端(MCP Server)
redirect_uri 精确匹配。客户端(MCP Client)
iss;resource,强制 PKCE(S256);平台与治理
MCP 做了一件很不一样的事:它把"连接"变成了默认能力,却把"信任"完整地留给了实现者。协议不会替你拒绝任何一个请求——它只规定了你拒绝时该返回什么。
所以别再问"我们接了 OAuth 没有"。更值得问的是:这张 token 是签给谁的、还能不能用到别处、下一次工具调用是谁授权的、以及当描述悄悄改了一个字时,审批记录还作不作数。
认证从来不是发给模型的一张通行证,而是给每一次工具调用划下的那条边界。
参考:MCP Specification 2026-07-28(Authorization、Security Best Practices、Key Changes);OWASP MCP Top 10;CVE-2026-61560、CVE-2026-54504;公开安全研究与扫描报告汇总(第三方数据口径不一,仅作趋势参考)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。