用授权服务器保护 MCP 服务器
MCP(Model Context Protocol,模型上下文协议)正迅速成为大型语言模型(LLM)与业务逻辑交互的标准 API。和 REST 诞生初期一样,开发者们跑得很快,经常为了功能而牺牲安全。
我们已经在这些新技术上看到了十多年前犯下的那些安全错误。
MCP 安全公告列表里就有不少例子,列出了路径遍历、路径校验和注入等漏洞。从租户隔离失效、网络配置错误,到工具投毒,这些负面影响都应当避免。由于智能体(agent)是自主行动的,一个配置错误的 MCP 服务器,其破坏力比一个 REST 端点大得多。
最简单的缓解办法是什么?使用授权服务器(Authorization Server,AS)来强制要求身份验证和授权。如果你的 MCP 服务器涉及任何机密内容,或者它提供的数据和功能会随请求用户不同而变化,那就把它牢牢锁住。公开文档或共享资源(比如我们的 FusionAuth 文档 MCP 服务器)不需要身份验证,但除此之外的任何东西,都需要一层真正的安全防护。
实际上需要两层防护:一层放在 MCP 服务器前端,这是本文将重点讨论的;另一层放在后端,标准只是顺带提及,并没有详细规定。
MCP 规范并没有在协议层面强制安全要求,所以「交由实现者处理」往往就变成了「先拖着,直到出了入侵事件才重新考虑」。别让这种事发生在你身上。
幸运的是,MCP 确实定义了一种用 OAuth 保护远程服务器的方法。看来我们还是从 REST 时代的错误中学到了一点东西。
这种方案把认证工作交给 AS 完成:在请求到达你的服务器之前,AS 会先确认用户的身份和权限。具体通过 OAuth 授权码模式(Authorization Code)实现,用到作用域(scopes)和授权同意(consents)这些大家熟悉的概念。虽然 MCP 有一个扩展支持客户端凭证(client credentials)访问,但本文重点讨论更常见的「代表用户」(on-behalf-of)流程:由用户明确授权 MCP 客户端代表自己访问 MCP 服务器。
下面就是让 MCP 客户端完成认证与授权、进而使用你受保护的远程 MCP 服务器的完整流程:
- MCP 服务器发现
- MCP 客户端注册
- 用户认证
- 用户授权
- 访问令牌签发
- 访问令牌校验
- 使用 MCP
我们逐项来说。
MCP 服务器发现
首先,需要把 MCP 服务器的地址配置到 MCP 客户端(比如 Claude Desktop 或 AI Agent)里。具体怎么配置取决于客户端,可能是编辑一个 JSON 文件,也可能是在表单里填一个 URL。
当客户端第一次访问 MCP 服务器时,服务器会回应:"我不认识你,请到这里来为我获取凭证。"这条重定向流程的技术细节由 RFC 9728 定义。
这个响应会把客户端导向一个 MCP 服务器信任的授权服务器(AS)。
客户端注册
MCP 客户端到达 AS 之后,AS 需要先了解它的一些情况。完成这一步有几种方式,按规范中的评估顺序排列:
-
提前在 AS 上注册 MCP 客户端。也就是说,由某个其他流程预先在客户端与 AS 之间建立了关联。这种方式被称为"带外"(out of band),听起来很神秘,实际意思是"我们不在乎它是怎么发生的,只要发生了就行"。这就好比出席一场正式晚宴,走到门口亮一下邀请函——你已经被邀请了,在跟门口的人开口之前,对方就知道你是谁。
-
在 MCP 客户端第一次与 AS 交互时完成注册。这里有两条技术路径:客户端 ID 元数据文档(Client ID Metadata Documents,CIMD)和动态客户端注册(Dynamic Client Registration,DCR)。前者更新一些,看起来是未来的方向;后者则被明确保留下来,用于向后兼容。这就像酒吧门口的保安查验驾照——只要满足一定条件,就能进去。
-
直接让用户提供凭证。这种方式我还没在实际环境中见过,它把协调工作完全推给了人。好比你没带邀请函就去参加聚会,主人得亲自走到门口替你作保。
选择哪种客户端注册方式,会直接影响你在安全性和便利性之间的取舍。
- 采用预注册流程,意味着你能事先掌握哪些 MCP 客户端可以和你的服务器交互,但每个客户端都需要提前配置好。
- 使用 CIMD 或 DCR 则可以省去不少麻烦:客户端可以即时注册,即使它从未用过这个授权服务器(AS)。不过别担心,这两种方式并不会把你的端点暴露给任意客户端——用户仍然必须在 AS 上拥有有效账号,并成功完成身份验证。
- 两种方式都允许你控制哪些 MCP 客户端可以注册,只是这个控制流程在两个规范里都没有完全定义清楚。
用户认证
AS 识别出 MCP 客户端之后,用户还需要登录。
认证过程可以采用 AS 认可的任何形式:密码、通行密钥(passkey)、第三方单点登录(SSO)、多因素认证(MFA)都可以。MCP 客户端并不关心具体用了哪种。
通常,AS 会向用户展示一个授权确认页面,列出 MCP 客户端所需的 OAuth scope(授权范围)。具体映射关系取决于你的实现,但通常一个 scope 对应一组 MCP 客户端可以访问的工具。
比如,account:read 这个 scope 可以映射到下面这些虚构的端点和方法:
GET /api/v1/account:获取已认证用户的完整账户信息GET /api/v1/account/usage:获取账户的使用量指标GET /api/v1/account/billing:读取账户的账单详情GET /api/v1/account/members:列出与账户关联的用户/成员(如果有)GET /api/v1/account/preferences:获取账户级别的偏好设置和配置选项
相比之下,account:write 这个 scope 则可能授予
权限范围(scope)分为必选和可选两种。不管哪种情况,只要用户觉得 MCP 客户端申请的权限太宽泛,都应该有权取消授权。
访问令牌的签发
用户在授权服务器(AS)上完成身份验证并同意授权后,AS 会向客户端签发一个有时效的 OAuth 访问令牌(Access Token)。MCP 客户端保存这个令牌,并在访问受保护的 MCP 服务器时把它出示给对方。
相比其他智能体工具,MCP 在安全上的一个好处是:令牌由 MCP 客户端持有,不会交给大语言模型(LLM)。请求发出时,MCP 客户端会自动把令牌附加进去,整个过程对上层透明。
这样一来,攻击者想通过诱导 LLM 直接套出凭据就行不通了。
访问令牌的校验
MCP 服务器收到令牌后,会从受众(audience)、过期时间、签发方(issuer)和允许的权限范围等方面进行校验。
例如,如果 AS 签发的访问令牌是 JWT(JSON Web Token),MCP 服务器还应该遵循 JWT 的最佳实践,比如验证签名。
访问 MCP 功能
令牌校验通过后,MCP 服务器会基于已验证的身份,继续执行所请求的调用。MCP 客户端响应用户原始请求所需的数据和功能,这时才真正可用。
正如前面所说,MCP 服务器背后的身份认证与授权层非常重要,但本文不打算展开,原因有三:
- 这部分和你的具体功能或数据强相关
- 你很可能已经有现成的实现
- MCP 规范里并没有深入涉及这部分
规范对更深层的环节只给了一条建议:不要把交给 MCP 服务器的访问令牌继续传给下游服务。但正确落实这个授权模型,对数据安全至关重要。
在把任何东西作为 MCP 服务器开放之前,先想清楚:哪些用户应该能访问哪些服务、数据和功能。
接下来是什么?
MCP 发展得很快,REST 时代犯过的安全错误已经在现实场景里重新出现。好在 MCP 规范并没有让你从零开始设计一套安全模型,而是直接站在 OAuth 这个久经考验的标准之上——它经过了多年的专家审查,背后还有成熟的生态系统支撑。
把身份认证和授权交给授权服务器(AS),你就能获得经过验证的身份、用户可控的授权同意,以及有时效性的访问令牌。
在构建 MCP 服务器时,有几条原则值得记在心里:
-
限制授权范围。如果 MCP 客户端请求了过宽的权限,那就等于在向 AI 索要 root 级访问权。设计 scope 时,应该围绕每个客户端实际所需的最小权限来制定。
-
把“未认证服务器”当作一个有意的选择。开放、无需认证的 MCP 服务器只适合公共资源,比如文档。如果提供的服务不止这些,就需要一层真正的安全防护。
-
不要止步于 MCP 边界。规范描述了 OAuth 如何保护客户端与服务器之间的握手,但你的 MCP 服务器很可能还会调用自己的下游服务。这些连接的安全,同样需要你来负责。
工具和基础设施已经就绪。请在你的 MCP 服务器出问题之前把它保护好,而不要等到事后才补救。