详解 MCP 远程服务器如何实现 OAuth 2.1 + PKCE 认证流程,解决生产环境多租户场景下的令牌安全和轮换问题。
本地通过 stdio 启动的 MCP Server 会继承机器上已有的凭证。Shell 里已有 AWS_PROFILE、DOCKER_HOST,还有 ~/.config 里的一堆 token,Server 直接拿来用就行。这也是为什么大多数团队很晚才发现 MCP 鉴权问题:第一个需要全公司共享的 Server 根本没有 Shell 可以继承凭证。
远程 MCP Server 基于 Streamable HTTP 通信,架在真实的域名后面,必须对每个调用方做身份验证。Model Context Protocol 定义了具体的鉴权方式:OAuth 2.1 搭配 PKCE、元数据发现和 Bearer Token。本文从头到尾走一遍完整流程,涵盖客户端发出的具体请求,以及如何配置才能让 Claude Desktop、Cursor 和 VS Code 无需手动 workaround 就能接入你的 Server。
一个看似方便的捷径是 https://mcp.example.com/mcp?token=...。五分钟内能用,之后每次安全审查都会挂掉:
MCP 客户端都遵循标准流程。实现一次,所有符合规范的客户端都能直接接入,无需定制集成。
一个受保护的 MCP 资源返回 401 Unauthorized,并带有 WWW-Authenticate 头,指向授权服务器的元数据:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
受保护资源文档告诉客户端授权发生在哪个地址,以及 Token 必须携带哪个 Audience:
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": ["https://auth.example.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["tools:read", "tools:run", "admin"]
}
客户端随后获取授权服务器的元数据,按惯例放在 /.well-known/oauth-authorization-server:
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"registration_endpoint": "https://auth.example.com/register",
"code_challenge_methods_supported": ["S256"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_basic"]
}
如果你已经在跑一个身份提供商,Auth0、Okta、Keycloak 和 AWS Cognito 都暴露了这些文档。你要做的只是配置路由,而不是自己写一个鉴权服务器。
MCP 客户端不是预先注册好的应用。首次连接时它们调用注册端点,获取一个 Client ID:
POST /register HTTP/1.1
Content-Type: application/json
{
"client_name": "Claude Desktop",
"redirect_uris": ["http://127.0.0.1:6273/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
{ "client_id": "mcp-local-9f3a2c", "client_secret_expires_at": 0 }
公开客户端不使用密钥。这是刻意设计的:桌面应用没法保密一个 Secret,所以 OAuth 2.1 依赖 PKCE 来保障安全。如果你的提供商禁用了动态注册,可以 out of band 发放一个 Client ID,让用户在 MCP URL 配置里传入;流程中其他部分保持不变。
客户端生成一个 Code Verifier 及其 SHA-256 Challenge,然后打开浏览器:
https://auth.example.com/authorize
?response_type=code
&client_id=mcp-local-9f3a2c
&redirect_uri=http://127.0.0.1:6273/callback
&scope=tools:read%20tools:run
&state=8xZ1...
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
用户同意后,浏览器重定向到回环地址并带上一个 Code。客户端用它换 Token,同时证明自己持有 Verifier:
curl -X POST https://auth.example.com/token \
-d grant_type=authorization_code \
-d client_id=mcp-local-9f3a2c \
-d code=SplxlOBeZQQYbYS6WxSbIA \
-d redirect_uri=http://127.0.0.1:6273/callback \
-d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "v1.MTQwYzI3...",
"scope": "tools:read tools:run"
}
从此以后,每次对 MCP 端点的 JSON-RPC 请求都携带 Bearer Token:
POST /mcp HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json
Accept: application/json, text/event-stream
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
Access Token 的生命周期应该是分钟级,而不是周级。Refresh Token 才是长期凭证,而且可以在服务端直接撤销,不影响客户端。
一旦 Server 暴露了横跨三个服务的二十个工具,扁平的 read/write Scope 会很快变得不够用。按能力划分 Scope 并在每次调用时做校验:
注册时把每个工具映射到一个 Scope,收到调用时直接拒绝并返回结构化错误,而不是让 Agent 在把东西搞坏之后才摸索到边界:
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32003,
"message": "token lacks scope tools:run:write",
"data": { "required_scope": "tools:run:write" }
}
}
接了几个内部 Server 之后,支撑工单里几乎全是以下四个问题:
元数据通过 HTTP 提供,或结尾斜杠不匹配。 Token 里的 Issuer 必须和 Issuer 字段完全一致,包括协议和主机名。
允许列表里没有回环地址重定向。 桌面客户端用 http://127.0.0.1:<port>/callback,拒绝回环 URI 会导致登录无法完成。
Token 没有 Audience。 多租户身份提供商默认签发的 Token 可以用于你拥有的所有应用,除非你设置了 aud 并做校验。
把时钟偏差当作致命错误。 在过期校验上至少留 60 秒的缓冲;笔记本时钟很容易漂移。
先用纯 HTTP 客户端把整个链路验证通。curl 元数据文档、在浏览器里跑授权流程、用 Token 调用 initialize 和 tools/list。等裸请求跑通之后,MCP Inspector 可以交互式地驱动 OAuth 流程。
当一个团队把 OpenAPI 文档作为托管 MCP Server 发布时,鉴权就是 Demo 和基础设施之间的分水岭:文档保持公开,而接触真实系统的工具则藏在 OAuth 后面,带有按用户划分的 Scope 和审计日志。发布那个端点只是从一份规范生成构建产物,而不是再实现一套安全方案。
如果你想看这个流程的托管端,参考从一份规范同时发布 API 文档和 MCP 端点(部署在你自己的域名上),以及 MCP stdio 与远程传输的对比(了解什么时候才真正需要托管部署)。也可以试试本地优先的工作流——Server 起在本地机器上,根本不存在鉴权面——在线 Demo 里有演示。