MCP 认证与授权完全指南:从 OAuth 2.1 到企业级实践
前言
Model Context Protocol(MCP)正迅速成为 AI 应用与外部工具、数据源之间的“通用连接层”。然而,当 AI 智能体开始调用退款审批 API、读写数据库、部署代码时,一个问题变得至关重要:如何确保只有授权的客户端和用户能够访问敏感资源?
本文将从架构基础到实战落地,系统讲解 MCP 的认证与授权机制,包括协议演进、核心角色、完整授权流程、不同传输场景的认证方式以及企业级实现方案,帮助你在实际项目中构建安全可控的 MCP 服务。
一、为什么 MCP 需要认证与授权?
1.1 从“能连”到“安全连”
早期的 MCP 版本(2024-11-05)并未内置授权支持。但随着开发者开始用 MCP 打通 AI 系统与生产环境 API,安全问题迅速浮现——AI 智能体可能被恶意提示词诱导调用危险工具,窃取敏感数据或执行未授权操作。
认证(Authentication)解决的是 “谁在发起请求” ,授权(Authorization)解决的是 “请求者能做什么” 。任何 MCP 客户端与服务器之间的请求,都必须经过这两个环节的校验。
1.2 协议版本演进
| 2024-11-05 | 无内置支持 | 仅通过环境变量传递凭据 |
| 2025-03-26 | 引入 OAuth 2.1 授权 | MCP 服务器作为资源服务器 |
| 2025-06-18 / 2025-11-25 | 授权机制持续优化 | 支持 OIDC Discovery、CIMD 等 |
MCP 选择 OAuth 2.1 作为授权协议基础,复用成熟的技术模式,让开发者不必从零发明新方案。
二、MCP 认证架构的核心角色
理解 MCP 的认证逻辑,首先需要理清四个核心角色的职责:
#mermaid-svg-0xNPa0AFzQ6nF0sp{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0xNPa0AFzQ6nF0sp .error-icon{fill:#552222;}#mermaid-svg-0xNPa0AFzQ6nF0sp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0xNPa0AFzQ6nF0sp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .marker.cross{stroke:#333333;}#mermaid-svg-0xNPa0AFzQ6nF0sp svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0xNPa0AFzQ6nF0sp p{margin:0;}#mermaid-svg-0xNPa0AFzQ6nF0sp .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .cluster-label text{fill:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .cluster-label span{color:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .cluster-label span p{background-color:transparent;}#mermaid-svg-0xNPa0AFzQ6nF0sp .label text,#mermaid-svg-0xNPa0AFzQ6nF0sp span{fill:#333;color:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .node rect,#mermaid-svg-0xNPa0AFzQ6nF0sp .node circle,#mermaid-svg-0xNPa0AFzQ6nF0sp .node ellipse,#mermaid-svg-0xNPa0AFzQ6nF0sp .node polygon,#mermaid-svg-0xNPa0AFzQ6nF0sp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .rough-node .label text,#mermaid-svg-0xNPa0AFzQ6nF0sp .node .label text,#mermaid-svg-0xNPa0AFzQ6nF0sp .image-shape .label,#mermaid-svg-0xNPa0AFzQ6nF0sp .icon-shape .label{text-anchor:middle;}#mermaid-svg-0xNPa0AFzQ6nF0sp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .rough-node .label,#mermaid-svg-0xNPa0AFzQ6nF0sp .node .label,#mermaid-svg-0xNPa0AFzQ6nF0sp .image-shape .label,#mermaid-svg-0xNPa0AFzQ6nF0sp .icon-shape .label{text-align:center;}#mermaid-svg-0xNPa0AFzQ6nF0sp .node.clickable{cursor:pointer;}#mermaid-svg-0xNPa0AFzQ6nF0sp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .arrowheadPath{fill:#333333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0xNPa0AFzQ6nF0sp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0xNPa0AFzQ6nF0sp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0xNPa0AFzQ6nF0sp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0xNPa0AFzQ6nF0sp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .cluster text{fill:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp .cluster span{color:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0xNPa0AFzQ6nF0sp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0xNPa0AFzQ6nF0sp rect.text{fill:none;stroke-width:0;}#mermaid-svg-0xNPa0AFzQ6nF0sp .icon-shape,#mermaid-svg-0xNPa0AFzQ6nF0sp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0xNPa0AFzQ6nF0sp .icon-shape p,#mermaid-svg-0xNPa0AFzQ6nF0sp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0xNPa0AFzQ6nF0sp .icon-shape .label rect,#mermaid-svg-0xNPa0AFzQ6nF0sp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0xNPa0AFzQ6nF0sp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0xNPa0AFzQ6nF0sp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0xNPa0AFzQ6nF0sp :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
MCP 认证架构
1. 获取访问令牌
2. 携带令牌请求
3. 验证令牌
用户/资源所有者
MCP 宿主应用如 Claude Desktop
MCP 客户端
MCP 服务器资源服务器
授权服务器独立/外置
角色详解:
| 资源所有者 | 授权客户端访问其受保护资源的实体,通常是终端用户 |
| MCP 客户端 | 代表资源所有者发起请求的应用程序 |
| MCP 服务器 | 托管受保护资源的服务器,作为 OAuth 2.1 资源服务器 |
| 授权服务器 | 负责验证用户身份并颁发访问令牌 |
在标准 MCP 授权流程中,MCP 服务器不颁发令牌,只负责消耗(验证)外部授权服务器颁发的访问令牌。
三、完整授权流程:从 401 到连接建立
MCP 的授权流程遵循 OAuth 2.1 规范,下面以流程图和文字结合的方式拆解每个步骤。
3.1 整体授权流程
用户/浏览器
授权服务器
MCP 服务器
MCP 客户端
用户/浏览器
授权服务器
MCP 服务器
MCP 客户端
#mermaid-svg-9qfJQof7LNPaZ0vw{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9qfJQof7LNPaZ0vw .error-icon{fill:#552222;}#mermaid-svg-9qfJQof7LNPaZ0vw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9qfJQof7LNPaZ0vw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9qfJQof7LNPaZ0vw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9qfJQof7LNPaZ0vw .marker.cross{stroke:#333333;}#mermaid-svg-9qfJQof7LNPaZ0vw svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9qfJQof7LNPaZ0vw p{margin:0;}#mermaid-svg-9qfJQof7LNPaZ0vw .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9qfJQof7LNPaZ0vw text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9qfJQof7LNPaZ0vw .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-9qfJQof7LNPaZ0vw .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-9qfJQof7LNPaZ0vw #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-9qfJQof7LNPaZ0vw .sequenceNumber{fill:white;}#mermaid-svg-9qfJQof7LNPaZ0vw #sequencenumber{fill:#333;}#mermaid-svg-9qfJQof7LNPaZ0vw #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-9qfJQof7LNPaZ0vw .messageText{fill:#333;stroke:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9qfJQof7LNPaZ0vw .labelText,#mermaid-svg-9qfJQof7LNPaZ0vw .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .loopText,#mermaid-svg-9qfJQof7LNPaZ0vw .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9qfJQof7LNPaZ0vw .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9qfJQof7LNPaZ0vw .noteText,#mermaid-svg-9qfJQof7LNPaZ0vw .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-9qfJQof7LNPaZ0vw .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9qfJQof7LNPaZ0vw .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9qfJQof7LNPaZ0vw .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9qfJQof7LNPaZ0vw .actorPopupMenu{position:absolute;}#mermaid-svg-9qfJQof7LNPaZ0vw .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-9qfJQof7LNPaZ0vw .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9qfJQof7LNPaZ0vw .actor-man circle,#mermaid-svg-9qfJQof7LNPaZ0vw line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-9qfJQof7LNPaZ0vw :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
1. 初始握手
2. 受保护资源元数据发现
3. 授权服务器发现
4. OAuth 2.1 with PKCE 流程
5. 访问受保护资源
MCP 请求(无令牌)
401 Unauthorized + WWW-Authenticate header
resource_metadata URL
GET /.well-known/oauth-protected-resource
返回 PRM 文档
{resource, authorization_servers, scopes_supported}
GET /.well-known/oauth-authorization-server
返回 ASM 文档
{authorization_endpoint, token_endpoint, jwks_uri}
打开浏览器跳转 authorization_endpoint
输入凭证,授权
回调并携带授权码
用授权码 + code_verifier 换 access_token
颁发 access_token
MCP 请求 + Bearer token
验证 JWT 签名、iss、aud、exp
返回响应
3.2 关键步骤详解
第一步:初始握手(401 质询)
客户端发起无令牌的请求后,MCP 服务器返回 HTTP 401,并通过 WWW-Authenticate 头部告知受保护资源元数据的地址:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://server.com/.well-known/oauth-protected-resource"
第二步:受保护资源元数据发现
客户端从该地址获取 PRM 文档,内容示例如下:
{
"resource": "https://server.com/mcp",
"authorization_servers": ["https://auth.server.com"],
"scopes_supported": ["mcp:tools", "mcp:resources", "calc.read", "calc.write"]
}
第三步:授权服务器发现
客户端根据 authorization_servers 字段,通过 OAuth 2.0 授权服务器元数据(RFC 8414)获取 authorization_endpoint、token_endpoint、jwks_uri 等信息。
第四步:OAuth 2.1 + PKCE 授权
这是最关键的环节。MCP 强制使用 PKCE(Proof Key for Code Exchange) 来保护授权码交换,无需客户端存储 client_secret:
客户端生成 code_verifier → 计算 code_challenge → 发送带 code_challenge 的授权请求
→ 用户登录并授权 → 返回授权码 → 用 code_verifier 换取 access_token
第五步:携带令牌访问资源
客户端将获取的 access_token 放入请求头:Authorization: Bearer <token>,MCP 服务器验证令牌后处理请求。
四、认证与授权方式分类
4.1 两种传输协议的不同认证策略
MCP 支持两种传输技术,认证策略差异显著:
| Stdio(本地进程) | 无显式认证;通过环境变量传递凭据 | 本地 MCP 服务器,作为客户端子进程运行 |
| Streamable HTTP(远程) | OAuth 2.1 + PKCE,遵循 RFC 9728 | 远程托管的 MCP 服务器,企业部署 |
对于 Stdio 传输,MCP 服务器作为客户端子进程启动,两者之间无需额外认证。后端服务的授权可通过环境变量配置 API Key 等方式实现。
4.2 两种认证模式对比
| API Key | 快速原型、本地部署、Demo 演示 | ⚠️ 低 | 权限无限制、轮换麻烦、无过期机制 |
| OAuth 2.1 + PKCE | 生产环境、多用户场景 | ✅ 高 | 需搭建授权服务器,复杂度高 |
API Key 虽然简单直接,但存在权限无限制、难以轮换、无法绑定具体用户等风险,生产环境强烈建议使用 OAuth 2.1。
五、企业级实现方案
5.1 架构模式:外部 IdP + JWKS 验证
在企业环境中,MCP 服务器通常不自己颁发令牌,而是委托给现有的身份提供商(IdP),仅负责验证令牌:
#mermaid-svg-VgyKVigiBWDdIvJf{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VgyKVigiBWDdIvJf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VgyKVigiBWDdIvJf .error-icon{fill:#552222;}#mermaid-svg-VgyKVigiBWDdIvJf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VgyKVigiBWDdIvJf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VgyKVigiBWDdIvJf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VgyKVigiBWDdIvJf .marker.cross{stroke:#333333;}#mermaid-svg-VgyKVigiBWDdIvJf svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VgyKVigiBWDdIvJf p{margin:0;}#mermaid-svg-VgyKVigiBWDdIvJf .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VgyKVigiBWDdIvJf .cluster-label text{fill:#333;}#mermaid-svg-VgyKVigiBWDdIvJf .cluster-label span{color:#333;}#mermaid-svg-VgyKVigiBWDdIvJf .cluster-label span p{background-color:transparent;}#mermaid-svg-VgyKVigiBWDdIvJf .label text,#mermaid-svg-VgyKVigiBWDdIvJf span{fill:#333;color:#333;}#mermaid-svg-VgyKVigiBWDdIvJf .node rect,#mermaid-svg-VgyKVigiBWDdIvJf .node circle,#mermaid-svg-VgyKVigiBWDdIvJf .node ellipse,#mermaid-svg-VgyKVigiBWDdIvJf .node polygon,#mermaid-svg-VgyKVigiBWDdIvJf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VgyKVigiBWDdIvJf .rough-node .label text,#mermaid-svg-VgyKVigiBWDdIvJf .node .label text,#mermaid-svg-VgyKVigiBWDdIvJf .image-shape .label,#mermaid-svg-VgyKVigiBWDdIvJf .icon-shape .label{text-anchor:middle;}#mermaid-svg-VgyKVigiBWDdIvJf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VgyKVigiBWDdIvJf .rough-node .label,#mermaid-svg-VgyKVigiBWDdIvJf .node .label,#mermaid-svg-VgyKVigiBWDdIvJf .image-shape .label,#mermaid-svg-VgyKVigiBWDdIvJf .icon-shape .label{text-align:center;}#mermaid-svg-VgyKVigiBWDdIvJf .node.clickable{cursor:pointer;}#mermaid-svg-VgyKVigiBWDdIvJf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VgyKVigiBWDdIvJf .arrowheadPath{fill:#333333;}#mermaid-svg-VgyKVigiBWDdIvJf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VgyKVigiBWDdIvJf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VgyKVigiBWDdIvJf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VgyKVigiBWDdIvJf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VgyKVigiBWDdIvJf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VgyKVigiBWDdIvJf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VgyKVigiBWDdIvJf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VgyKVigiBWDdIvJf .cluster text{fill:#333;}#mermaid-svg-VgyKVigiBWDdIvJf .cluster span{color:#333;}#mermaid-svg-VgyKVigiBWDdIvJf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VgyKVigiBWDdIvJf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VgyKVigiBWDdIvJf rect.text{fill:none;stroke-width:0;}#mermaid-svg-VgyKVigiBWDdIvJf .icon-shape,#mermaid-svg-VgyKVigiBWDdIvJf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VgyKVigiBWDdIvJf .icon-shape p,#mermaid-svg-VgyKVigiBWDdIvJf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VgyKVigiBWDdIvJf .icon-shape .label rect,#mermaid-svg-VgyKVigiBWDdIvJf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VgyKVigiBWDdIvJf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VgyKVigiBWDdIvJf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VgyKVigiBWDdIvJf :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
企业认证架构
JWT token
Bearer JWT
获取 JWKS
验证签名
响应
用户
IdPOkta/Auth0/Entra ID
MCP 客户端
MCP 服务器
MCP 服务器的工作流程:
5.2 精细授权:基于 Scope 的 RBAC
OAuth 2.0 的 scope 机制天然支持 RBAC。以下是一个典型配置:
| calc.read | 只读计算器工具 | 普通用户 |
| calc.write | 创建/修改计算 | 高级用户 |
| admin | 管理权限 | 系统管理员 |
MCP 客户端获取令牌时请求所需 scope,服务器验证 token 中的 scope 是否符合操作要求。
5.3 机器对机器场景:Client Credentials 扩展
对于 CI/CD 流水线、后台服务等无人值守场景,标准 OAuth 授权流程(需要用户浏览器交互)不再适用。MCP 提供了 OAuth Client Credentials 扩展,支持应用层凭据(client_id + client_secret 或 JWT 断言)直接换取令牌。
# 示例:使用 client_credentials 获取 token
curl -X POST https://auth.server.com/token \\
-d "grant_type=client_credentials" \\
-d "client_id=mcp-client" \\
-d "client_secret=your-secret" \\
-d "scope=calc.read"
六、安全最佳实践(OWASP 标准)
6.1 原则一:最小权限原则
每个 MCP 服务器只应获得完成其功能所必需的最小权限集,绝不授予超出需要的 scope。
6.2 原则二:端到端加密与短生命周期令牌
- 对所有通信启用 TLS 加密
- 使用短生命周期的访问令牌(如 1 小时内过期),配合刷新令牌续期
- 定期轮换 JWT 签名密钥和 client_secret
6.3 原则三:Token Audience(受众)校验
每个 token 都必须验证 aud 是否匹配预期的 MCP 服务器,防止 token 被重用到其他服务:
if (!tokenClaims.getAudience().contains("mcp-server")) {
throw new SecurityException("Invalid audience");
}
6.4 原则四:强制用户明确 Consent
每一次授权都需获取用户的明确确认,防止攻击者通过伪造授权请求骗取令牌。
6.5 其他安全要点
- 本地运行时应仅绑定到 localhost(127.0.0.1),防止外部访问
- 使用 OpenID Connect Discovery 1.0 增强授权服务器发现
- 对所有工具输入进行验证、实施速率限制、清理输出内容
七、实操指南:从零为 MCP 服务配置认证
7.1 准备工作
确认你的 MCP 服务器需要满足以下要求:
- 使用 Streamable HTTP 传输(非 stdio)
- 能够暴露 /.well-known/oauth-protected-resource 和 /.well-known/oauth-authorization-server 端点
7.2 步骤一:为 MCP 服务器配置受保护资源元数据
在服务器根路径创建 /.well-known/oauth-protected-resource 端点,返回以下内容:
{
"resource": "https://your-mcp-server.com/mcp",
"authorization_servers": ["https://auth.your-company.com"],
"scopes_supported": ["mcp:tools", "calc.read", "calc.write"],
"bearer_methods_supported": ["header"]
}
7.3 步骤二:配置授权服务器支持 PKCE
确保授权服务器:
- 支持 OAuth 2.1 授权码流程
- 要求并验证 PKCE(code_challenge 和 code_verifier)
- 支持 OpenID Connect Discovery 或 RFC 8414 元数据
7.4 步骤三:在 MCP 服务器中实现令牌验证
选择适合你技术栈的实现方案:
方案一:使用 FastAPI-MCP(Python)
from fastapi_mcp import MCPMiddleware
app.add_middleware(MCPMiddleware, auth_url="https://auth.server.com")
方案二:Spring AI + OAuth2 Resource Server(Java)
# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.server.com
方案三:Dapr Bearer 中间件(云原生)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: bearer
spec:
type: middleware.http.bearer
version: v1
metadata:
– name: issuer
value: "https://auth.server.com"
7.5 步骤四:测试验证
八、总结与选型建议
| 本地开发/原型验证 | API Key + 环境变量 | 快速上手,成本低 |
| 远程生产部署 | OAuth 2.1 + PKCE | 安全性高,支持多用户 |
| 企业内部已有 IdP | JWKS 令牌验证 | 复用现有身份体系,无状态验证 |
| CI/CD / 机器访问 | OAuth Client Credentials | 支持无人值守自动化 |
| 多团队多租户 | CIMD(Client ID Metadata Documents) | 更强的客户端隔离和治理 |
核心要点回顾:
- MCP 将认证与授权职责分离给外部授权服务器,自身专注于作为资源服务器验证令牌
- 强制使用 PKCE 的 OAuth 2.1 授权码流程,无需硬编码客户端密钥
- 本地 stdio 传输无需认证,远程 HTTP 传输必须遵循完整的 OAuth 授权流程
- 生产环境应结合 JWKS、scope RBAC、令牌受众校验,构建多层防御
随着 MCP 规范持续演进(如 CIMD 逐步替代动态客户端注册、OIDC Discovery 增强),开发者应密切关注官方更新,确保安全策略与时俱进。
参考资料
- MCP Specification 2025-11-25 – Authorization
- OWASP MCP Security Cheat Sheet
- Baeldung – MCP Authorization With Spring AI and OAuth2
- A Developer‘s Guide to MCP Authentication
- Building Enterprise-Ready MCP Servers with JWKS
- OAuth 2.1 Draft Specification
