给你的 MCP Server 加上鉴权:OAuth 实战

你的 MCP Server 谁都能调吗?如果它只是读几个公开文档,无所谓;但一旦接上真数据(数据库、内部 API、文件系统)或真动作(发消息、改配置、下订单),「谁能调用它」就是这个系统的第一道安全边界。

这道边界的位置取决于传输方式。MCP 规范对两种主流传输给出了截然不同的姿态:本地 stdio 的信任边界在本机——进程是你亲手启动的,规范明确说 stdio 传输不应该走那套 OAuth 流程,凭证直接从环境取;而远程 HTTP Server 面对的是「任何摸得到网络的人」,规范要求 HTTP 传输遵循完整的授权框架(授权本身在 MCP 里是可选能力,但 HTTP 传输一旦要做,就该按这套来)。本文把这套框架的演进讲清楚(规范细节截至 2026-10-07,现行版本 2026-07-28),再给出三种典型部署的鉴权选型和一段可本地跑通的最小实现。

规范演进:一年半,三步走

MCP 的授权定义在传输层,演进线索很清晰:

2025-06-18 版:奠基。 首次为 HTTP 传输定义基于 OAuth 2.1(草案 draft-ietf-oauth-v2-1-13)的授权框架,三个角色各就各位——MCP Server 是 OAuth 的资源服务器(RS),MCP Client 是 OAuth 客户端,授权服务器(AS)负责认证用户并签发 token。MCP Server 必须实现 RFC 9728「受保护资源元数据」:收到无凭证请求时返回 401,并用 WWW-Authenticate 头告诉客户端去哪发现配套的 AS;客户端发起授权时必须带上 RFC 8707 的 resource 参数,把 token 的受众绑定到这个 MCP Server。这一版还把动态客户端注册(DCR,RFC 7591)列为推荐能力,解决「客户端不可能预先认识所有 AS」的问题。

2025-11-25 版:补细节。 新增 OpenID Connect Discovery 支持;引入增量 scope 授权——401/403 的 WWW-Authenticate 里可携带 scope 引导客户端按需补权限(step-up);引入 OAuth Client ID Metadata Documents(CIMD:客户端把一份 JSON 元数据文档挂在自己控制的 HTTPS 域名下,URL 即 client_id)作为推荐的注册机制;同时把 WWW-Authenticate 头从必选放宽为可选、可用 .well-known 端点兜底发现。

2026-07-28 版(现行):无状态化并收紧。 传输层大改:去掉会话与 initialize 握手,每个请求自带协议版本与凭证,鉴权随之变成纯粹的每请求校验。授权侧两个关键变化:其一,DCR 正式废弃(仅保留向后兼容),客户端注册改为三选一——CIMD(推荐)、预注册、DCR(兼容不支持 CIMD 的旧 AS);其二,引入 RFC 9207 的 iss 校验——AS 应在授权响应中带上 iss 声明「这响应是我发的」,客户端在兑换授权码前必须将其与记录的签发方比对,防 mix-up 攻击(规范预告未来版本会把 AS 的 SHOULD 升级为 MUST)。PKCE 细则也收紧:必须用 S256 方法,且发现 AS 不支持 PKCE 时客户端必须拒绝继续。

背景板是 OAuth 2.1 本身的取向:PKCE 对所有授权码流程强制(不再只限公共客户端)、砍掉 implicit 与密码模式、redirect URI 精确匹配——MCP 直接受益于这些收紧。

三种部署,三套方案

本地 stdio:环境变量就是方案。 信任边界在本机,能读你进程环境变量的人已经拿到了你本机。做法是把 token/API key 经环境变量注入(配合系统钥匙串或密码管理器),别写进 shell 配置明文,也别让工具把 env 原样打日志。规范原话:stdio 传输 SHOULD NOT 走 HTTP 那套授权规范,从环境取凭证即可。

公司内网 HTTP:静态 token 或 mTLS。 内网场景不必一上来就上完整 OAuth。静态 Bearer token + 中间件校验,实现成本最低,适合服务间调用;代价是没有个体身份区分、撤销不灵活。mTLS 把身份做进证书层、双向可验,适合服务网格环境。若公司已有统一 IdP,也可以直接让 server 当 RS 复用企业 SSO,一步到位。

面向公网:完整 OAuth 2.1 流程,没有捷径。 流程是规范画好的:客户端裸连 → 401 + WWW-Authenticate(带 resource_metadata 地址)→ 拉 /.well-known/oauth-protected-resource 找到 AS → 拉 AS 元数据(RFC 8414 或 OIDC Discovery,客户端两者都得支持)→ 客户端注册(CIMD 或预注册)→ 浏览器跳转授权(授权码 + PKCE + resource 参数)→ 校验 iss、用 code 换 token → 之后每个请求都带 Authorization: Bearer。server 侧必须校验 token 受众:aud 里没有自己就拒绝。

动手:先加一层 Bearer 校验

不用一上来就搭 AS,先用 30 行代码把「无凭证拒之门外」做出来(下面这段本机已跑通验证):

// server.mjs —— 最小可跑:给 MCP 端点加 Bearer token 校验
import express from 'express';
import { timingSafeEqual } from 'node:crypto';

const app = express();
const TOKEN = process.env.MCP_TOKEN;
if (!TOKEN) {
  console.error('请先设置 MCP_TOKEN 环境变量');
  process.exit(1);
}

// 恒定时间比较,避免逐字节比对的时序侧信道
function tokenEquals(a, b) {
  const ba = Buffer.from(a);
  const bb = Buffer.from(b);
  return ba.length === bb.length && timingSafeEqual(ba, bb);
}

app.post('/mcp', express.json(), (req, res) => {
  const [scheme, token] = (req.headers.authorization ?? '').split(' ');
  if (scheme !== 'Bearer' || !token || !tokenEquals(token, TOKEN)) {
    res.set('WWW-Authenticate', 'Bearer resource_metadata="http://localhost:9010/.well-known/oauth-protected-resource"');
    return res.status(401).json({
      jsonrpc: '2.0', id: req.body?.id ?? null,
      error: { code: -32000, message: 'Unauthorized' },
    });
  }
  res.json({ jsonrpc: '2.0', id: req.body?.id ?? null, result: { tools: [] } });
});

app.listen(9010, () => console.log('MCP server listening on :9010'));

用 curl 验证三种情形:

MCP_TOKEN=s3cret node server.mjs

# 无 token、错误 token:都返回 401,响应头带 WWW-Authenticate
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:9010/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# 401
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:9010/mcp \
  -H 'Authorization: Bearer wrong-token' -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# 401

# 正确 token:200
curl -s -X POST http://localhost:9010/mcp \
  -H 'Authorization: Bearer s3cret' -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# {"jsonrpc":"2.0","id":1,"result":{"tools":[]}}

注意 401 响应里带了 WWW-Authenticate 和 resource_metadata——这不是装饰,是规范的发现入口:真正的 MCP 客户端就靠它找到你的授权服务器。

接入完整 OAuth:SDK 已把路铺好

静态 token 撑不到公网。接完整 OAuth 时,MCP Server 的角色是资源服务器——只验 token、不发 token,签发交给专门的 IdP。官方 TypeScript SDK 把这层做成了中间件(v2 起由 @modelcontextprotocol/server / @modelcontextprotocol/express 包提供):

// 伪代码:用官方 SDK 把 Bearer 校验升级为完整 OAuth 资源服务器
import { requireBearerAuth } from '@modelcontextprotocol/server';

const auth = requireBearerAuth({
  // 校验逻辑自选:本地验 JWT 签名,或走 RFC 7662 内省,或调 IdP
  verifier: {
    verifyAccessToken: async (token) => ({
      token,
      // expiresAt 必填:缺失时中间件直接按 401 处理
      expiresAt: Math.floor(Date.now() / 1000) + 3600,
      clientId: 'my-mcp-client',
      scopes: ['mcp:read'],
      resource: 'https://mcp.example.com/mcp', // aud 命中自己才放行
    }),
  },
  requiredScopes: ['mcp:read'],
  expectedResource: new URL('https://mcp.example.com/mcp'),
  resourceMetadataUrl: new URL('https://mcp.example.com/.well-known/oauth-protected-resource'),
});
// 中间件行为:缺/坏 token → 401 invalid_token;scope 不够 → 403 insufficient_scope,
// 均携带 WWW-Authenticate 挑战;SDK 还提供元数据路由,自动吐 RFC 9728 文档。

骨架不变:仍是「401 引导发现 → 客户端拿真 token → 每请求校验」,只是 token 从共享密钥换成了 AS 签发、受众绑定、带 scope 的访问令牌。

常见坑:规范明令禁止的和容易做错的

token passthrough 与混淆代理人(confused deputy)。 这是规范明文禁止的模式:MCP Server 收到客户端的 token 后原样转发给上游 API。规范写死了「MCP servers MUST NOT accept or transit any other tokens」——上游该拿的是另一个独立 token,由 MCP Server 以自己的身份向上游授权服务器申请。原样透传的后果是上游分不清「这是 MCP Server 在调」还是「客户端借 MCP Server 的壳在调」,攻击者甚至能借被劫持的授权码从上游直接换出 token。

token 泄漏到日志。 把 Authorization 头整个打进访问日志或错误堆栈,等于把门钥匙复印一份贴在墙上。规范还禁止把 token 放 URL 查询串(URL 会进日志与 Referer),token 只能走 Authorization 头。AS 侧应签短效访问令牌缩小泄漏窗口,server 侧要做日志脱敏;公共客户端的 refresh token 必须轮换。

scope 设计过粗。 一个 admin scope 走天下的 server,等于给每个客户端都发了万能钥匙。规范推荐最小权限起步、step-up 增量授权:平时只给读权限,需要写时再经 403 + insufficient_scope 挑战引导补授权。

把用户级授权当服务级授权。 典型场景是带静态 client ID 的代理型 server:它拿「服务自己注册的身份」去上游换 token,却没意识到同意书是签给服务的、不是签给每个用户的。规范要求这种代理对每个动态注册的客户端先取得用户同意再转发——否则一个用户的授权会被所有走过这个代理的客户端共享。

鉴权只是第一道门

鉴权回答的是「你是谁」,不回答「你能对这个 server 做什么」。门后的工具级权限、沙箱与最小信任设计,站内《给 Agent 上锁:工具权限、沙箱与最小信任设计》已经系统讲过——两道门一起上,MCP Server 才敢接真数据、真动作。

参考资料

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?