跳到主要内容

安全 — 从 Basic Auth 到 OAuth2.1

这一章讲三件事: 服务器上网之后,「谁可以用」这个问题有哪几档答案; 三档答案各自怎么工作、各自防住什么、漏掉什么; 以及 SDK 帮你把最难的那档(OAuth)简化成了哪两个零件。 前面章节的 Server 全是「来者不拒」的,这一章给它们装上门卫。

1. 先分档:不是所有数据都配同一个门卫

书里开篇先给了一张「按数据敏感度分档」的表——安全措施不是越多越好,是越配越好1:

你的服务器装的是风险够用的措施
公开数据(文档、目录)可以不要;怕爬虫滥用就发个 API key 登记
半公开半内部HTTPS + API key,护住敏感的那部分
个人敏感数据(健康、财务)HTTPS + OAuth 2.0/2.1 + 加密 + RBAC(按角色控权),还要过 GDPR、HIPAA 这类法规

(HTTPS 就是加过密的 HTTP,链路上别人偷看不到内容; GDPR、HIPAA 分别是欧盟的数据保护法和美国的医疗信息法—— 它们的细节本书没展开,知道「碰个人数据就有法规管」即可。)

本章主走查:同一个「门卫检查」的位置,三级梯子各怎么填—— 以及最后 OAuth 那级,一次完整的「授权码(用户在授权页面点头之后, 授权服务器发给客户端的一串一次性凭证,凭它才能换令牌)换令牌」流程长什么样。

2. 第一级:Basic Auth,最低成本的门卫

先交代一个小词:字节(byte)——计算机存数据的最小单位,八个 0/1 位拼成一个; 任何文字、密码在计算机里都是一串字节。

基本认证(Basic Auth):每个请求的 Authorization 头里带上 Basic + 一段 base64(一种把任意字节转成纯文本字符的编码方式—— 注意:是编码不是加密,谁都能解开)编码的「用户名:密码」; 服务器解开核对,不对就拒2。常见变体是把「用户名:密码」换成一个 API key,意思相同2

落到 MCP 服务器上,就是给 Express 挂一个中间件—— 中间件是「每个请求进门都要先过一遍」的函数,它可以放行、拒绝、或给请求加点东西3:

app.use((req, res, next) => {
if (!req.headers["authorization"]) { res.status(401).send('Unauthorized'); return; }
if (!isValid(req.headers["authorization"])) { res.status(403).send('Forbidden'); return; }
next(); // 放行,继续往 /mcp 路由走
});

两个拒绝码别混:401 是「你还没自报家门」,403 是「报了,但我不认」3。 客户端一侧,把自定义头交给传输层即可(requestInit.headers)4

这一级防什么?防「路过的人顺手就用」。 漏什么?密钥在网上裸奔(base64 一解就开, 必须配 HTTPS)、没法说「你能读不能写」、泄露了只能整体换5。 所以它配的是表里「中低风险」那一档。

3. 第二级:JWT,把权限签进令牌里

JWT(JSON Web Token)是一张「自包含的凭证」: 把「你是谁、你能干什么、什么时候过期」写成一段 JSON,用密钥签了名, 谁拿到都能当场验真伪——服务器不用存任何会话记录5。 它由三段组成,用点隔开6:

xxxxx.yyyyy.zzzzz
头部 载荷 签名
│ │ └─ 用密钥对前两段的签名:改一个字,签名就对不上
│ └─ claims(声明):sub(谁)、scopes(能干啥)、iat(何时签发)、exp(何时过期)
└─ 算法与类型,如 {"alg":"HS256","typ":"JWT"}

比 Basic 强在哪?载荷里能写「权限范围」(scopes)—— 比如 ["User.Read"] 就是「只许读用户数据」; 服务器验完签名,顺手查 scope,就知道这次调用该不该批—— 从「你是谁」升级到了「你能干什么」5。 加上签名防伪、过期自动失效、服务器无状态好扩容,这级梯子覆盖了大多数内部系统5

代码上用 jsonwebtoken 库一行签、一行验7:

const token = jwt.sign(payload, secret, { algorithm: "HS256" }); // 签
const decoded = jwt.verify(token, secret, { algorithms: ["HS256"] }); // 验:真伪+过期一次查

书里还列了「结构之外还该查什么」:iss(签发方,是不是你信任的那个)、 aud(受众,这张令牌是不是签给你这台服务器的)、nbf(生效时间)、 scopes/roles——并提醒 scope 通常比 role 细(role 是「管理员/普通用户」这种大类, scope 是「User.Read」这种具体许可)8。 「令牌是不是签给你这台服务器的」这一条,我们协议书架的规范拆解单独立了红线: 令牌必须验受众,否则一台服务器的令牌能被拿去打另一台——这叫混淆代理: 另一台服务器被蒙蔽,替拿着别人令牌的家伙当了代理人9

4. 第三级:OAuth2.1,把「发牌」也外包出去

JWT 还有个没回答的问题:令牌最初谁发?怎么发?用户不乐意了怎么收回? OAuth2.1 是一套授权框架,把「发令牌」这件事独立成第三方:授权服务器10。 于是舞台上有了三个角色10:

角色是谁干什么
资源服务器你的 MCP 服务器护着数据,验令牌
客户端MCP 客户端拿令牌来用数据
授权服务器专门的身份服务(Auth0、Entra ID、Cognito 这类)让用户登录、问用户「授权吗」、发令牌、管吊销

主走查:一次完整的授权码(authorization code)流程—— 授权码是授权服务器先发给客户端的一张「一次性兑换券」,凭它再换正式令牌。 书里给了一个可运行的四步脚本,我们跟着走11:

① GET 授权服务器的 /authorize
带上 client_id=abc、redirect_uri=http://localhost:3000/callback、
state=xyz、code_challenge=123
← 用户在授权服务器登录并同意 → 授权服务器把浏览器跳回 redirect_uri,
网址上挂着 ?code=SplxlOBeZQQYbYS6WxSbIA ← 这就是授权码

② 从跳转网址里取出 code

③ POST 授权服务器的 /token
grant_type=authorization_code & code=… & code_verifier=123
← 换回正式的访问令牌(access token——之后每次调资源服务器都要出示的那张票)

④ GET 资源服务器的 /userinfo,头部 Authorization: Bearer <访问令牌>
← 资源服务器验票(真伪、过期、scope、受众) → 交出数据

为什么兜这一圈,不让用户直接把密码给客户端? 这就是 OAuth 的核心思想——委派: 用户在授权服务器那里点头「我同意这个客户端读我的资料」, 客户端拿到的是一张范围有限、随时可吊销的令牌; 用户的密码从头到尾没离开过授权服务器10。JWT 在这里通常就充当那张令牌的格式10

书里第 ① 步有一个必须点名的瑕疵:code_challenge_method=plain12。 这是 PKCE(发音「pixy」,授权码流程的防伪插件:客户端先存一个秘密 code_verifier, 把它的变形 code_challenge 随第 ① 步发出,第 ③ 步再亮出原秘密核对) 的一种取值——plain 表示「变形就是原文」,等于没变形。

这里要补一个词:算法就是一套固定的计算步骤——输入进去、按步骤算、结果出来。

规范要求用 S256——用哈希(一种把任意输入压成固定长度指纹、 且没法从指纹倒推回原文的算法)做变形。书里这是教学简化,照抄到生产就是一个真漏洞13

SDK 帮你把第三级做成了两个零件,不用自己写上面的四步14:

const proxyProvider = new ProxyOAuthServerProvider({
endpoints: { authorizationUrl: "…", tokenUrl: "…", revocationUrl: "…" },
verifyAccessToken: async (token) => ({ token, clientId: "123", scopes: [] }),
getClient: async (client_id) => ({ client_id, redirect_uris: [] })
});
app.use(mcpAuthRouter({ provider: proxyProvider,
issuerUrl: new URL("http://auth.external.com"), // 授权服务器在哪
baseUrl: new URL("http://mcp.example.com") })); // 资源服务器(你)是谁

ProxyOAuthServerProvider 负责跟外部授权服务器对接(三个网址各管授权、换牌、吊销), mcpAuthRouter 把整套 OAuth 路由挂进你的 Express——你写的是配置,不是流程14。 我们协议书架的规范拆解确认:MCP 的授权模型正是「资源服务器 + 外部授权服务器」这个分工9

5. 收尾两问:门卫站哪、站多细

梯子装好,上线前还有两问15:

门卫站哪一层? 三个候选:web 服务器前面(普通中间件)、MCP 服务器上(SDK 的 auth 件)、 或更外面的网关(反向代理,统一挡在一切服务之前; 云厂商的 API 管理网关还附带内容安全、语义(内容的含义)缓存这类 AI 专属功能)。 流量统一、多团队共用,选网关;单服务自己管,选 SDK15

查得多细? 通过门卫只是「能进大楼」——每个工具还该各自查 scope: get_orders 也许 User.Read 就够,place_order 可能得要 Order.Write。 书里明说:不同工具可能需要不同权限,这个检查得你自己加15

6. 作者的判断与证据

有证据的:

  • 分档表、Basic/JWT/OAuth 的机制与代码、mcpAuthRouter 配置,书里有完整示例12371114;
  • 「SDK 支持的是 OAuth2.1」「按工具检查权限要加代码」是书中原话1415

作者的判断:

  • 三级梯子的递进讲法(Basic → JWT → OAuth)是教学排序,不是行业标准路径—— 很多系统从 JWT 起步直接配外部 IdP;
  • 推荐「授权服务器用现成的」(Entra ID、Cognito、Auth0),是作者反复强调的立场16—— 他给的延伸资源全部来自微软的 mcp-for-beginners 课程(他本人维护,见总纲)。

判断(我们的,不是书里的): 这一章对「MCP 服务器作者」的真正价值不在三段教程 (那些任何 web 安全书都有),而在第 5 节那两问——站哪层、查多细—— 以及一个本书没强调但规范写死的点:验令牌必须验受众, 否则你的服务器会变成别人令牌的跳板9如果错,会错在: 如果你的服务器只在公司内网跑、前面已有统一网关, 第三级可能确实是过度工程——分档表自己也是这么说的。安全没有绝对答案,只有配对。

7. 边界与局限

  • 全部示例是对称密钥(HS256):签发和验证用同一把 secret—— 适合单系统教学;跨组织的真实 OAuth 用非对称密钥(私钥签、公钥验),书里没讲 (补充,不在书里,来自通用知识);
  • 吊销、刷新令牌(refresh token)、令牌的存储安全,书里只在 OAuth 配置里露出 revocationUrl 一个名字,没展开;
  • STDIO 传输的安全(本地子进程模型)本章完全不覆盖——第 07 章消费侧四底线和 第 11 章的沙箱话题各管了一部分;
  • code_challenge_method=plain 瑕疵,见第 4 节;
  • prompt injection(提示注入——把恶意指令藏进数据里骗模型)这类 AI 特有的攻击, 本章没讲,书的资源链接(章末列的参考网址——这里是「书的参考资料」那个日常意思, 不是 MCP 协议里那种叫「资源链接」的消息内容)指向作者自己的课程16

8. 可带走的

  1. 先分档再选措施:公开 → 半公开 → 敏感,别过度也别欠费1;
  2. Basic Auth:Authorization: Basic base64(账号);401 没报名、403 不认;
  3. JWT = 头部.载荷.签名;scopes 让「你是谁」升级成「你能干什么」; 验令牌:签名、过期、issaud(受众)、scope;
  4. OAuth2.1 三方:资源服务器(你)、客户端、授权服务器; 授权码 = 一次性兑换券;密码不离开授权服务器;令牌可吊销;
  5. PKCE 用 S256,书里 plain 是教学简化,别照抄;
  6. SDK 两个件:ProxyOAuthServerProvider(对接外部授权服务器)+ mcpAuthRouter(挂路由);
  7. 上线两问:门卫站哪层(服务器/网关)、每个工具各查什么 scope;
  8. 授权服务器别自己写:Auth0、Entra ID、Cognito、Keycloak 都是现成品16

9. 原文地图

主题原书章原文位置
分档表Securing Your Applicationtext/13-fm-securing-your-application.txt:17(搜「Sensitivity level」)
Basic Auth 原理同上text/13-fm-securing-your-application.txt:123(搜「Authorization」)
中间件 401/403同上text/13-fm-securing-your-application.txt:301(搜「Unauthorized」)
客户端自定义头同上text/13-fm-securing-your-application.txt:414(搜「requestInit」)
JWT 好处同上text/13-fm-securing-your-application.txt:491(搜「scopes」)· :500(搜「Stateless」)
JWT 三段同上text/13-fm-securing-your-application.txt:546(搜「three parts separated by dots」)
sign/verify同上text/13-fm-securing-your-application.txt:650(搜「jsonwebtoken」)
还该查什么同上text/13-fm-securing-your-application.txt:743(搜「issuer」)· :787(搜「more granular than roles」)
OAuth 三方同上text/13-fm-securing-your-application.txt:995(搜「Resource server」)
委派与吊销同上text/13-fm-securing-your-application.txt:1015(搜「delegate access」)
授权码四步同上text/13-fm-securing-your-application.txt:1162(搜「Validate token or obtain authorization code」)· :1230(搜「Simulate browser redirect」)
plain 瑕疵同上text/13-fm-securing-your-application.txt:1238(搜「code_challenge_method=plain」)
SDK 两个件同上text/13-fm-securing-your-application.txt:1072(搜「ProxyOAuthServerProvider」)
门卫站哪层同上text/13-fm-securing-your-application.txt:1363(搜「putting the authorization component in front of」)
按工具查权限同上text/13-fm-securing-your-application.txt:1389(搜「per tool」)

Footnotes

  1. 出处:「Securing Your Application」第 15-65 段(text/13-fm-securing-your-application.txt:23,搜「Public data that anyone can access」;:43,搜「OAuth 2.0/2.1, encryption, and RBAC」)。 2 3

  2. 出处:「Securing Your Application」第 99-160 段(text/13-fm-securing-your-application.txt:131,搜「Base64-encoded」;:145,搜「API key instead」)。 2 3

  3. 出处:「Securing Your Application」第 283-376 段(text/13-fm-securing-your-application.txt:301,搜「Unauthorized」;:324,搜「401」)。 2 3

  4. 出处:「Securing Your Application」第 404-465 段(text/13-fm-securing-your-application.txt:414,搜「requestInit」)。

  5. 出处:「Securing Your Application」第 471-536 段(text/13-fm-securing-your-application.txt:477,搜「more granular control」;:504,搜「Stateless」)。 2 3 4

  6. 出处:「Securing Your Application」第 538-636 段(text/13-fm-securing-your-application.txt:546,搜「three parts separated by dots」)。

  7. 出处:「Securing Your Application」第 638-731 段(text/13-fm-securing-your-application.txt:666,搜「jwt.sign」;:713,搜「jwt.verify」)。 2

  8. 出处:「Securing Your Application」第 733-791 段(text/13-fm-securing-your-application.txt:743,搜「issuer」;:789,搜「more granular than roles」)。

  9. 补充(不在书里,依据我们的 protocol 书架):规范把令牌受众校验列为安全红线;混淆代理是 MCP 授权模型的专门风险。 依据: shelf=ai-protocol-reference/mcp-spec#06-authorization-and-security.md 事实=该章讲 OAuth 2.1 资源服务器模型与令牌受众等安全红线。 2 3

  10. 出处:「Securing Your Application」第 979-1027 段(text/13-fm-securing-your-application.txt:995,搜「Resource server」;:1015,搜「delegate access」;:1019,搜「revoke access at any time」)。 2 3 4

  11. 出处:「Securing Your Application」第 1154-1351 段(text/13-fm-securing-your-application.txt:1162,搜「Validate token or obtain authorization code」;:1282,搜「Call resource server」)。 2

  12. 出处:「Securing Your Application」第 1238 段(text/13-fm-securing-your-application.txt:1238,搜「code_challenge_method=plain」)。

  13. 补充(不在书里,来自通用知识):OAuth 2.1 要求公共客户端使用 PKCE,S256 是标准方法;plain 使 code_verifier 拦截攻击成为可能。

  14. 出处:「Securing Your Application」第 1029-1152 段(text/13-fm-securing-your-application.txt:1033,搜「supported by the MCP SDK」;:1071,搜「ProxyOAuthServerProvider」;:1097,搜「mcpAuthRouter」)。 2 3 4

  15. 出处:「Securing Your Application」第 1353-1391 段(text/13-fm-securing-your-application.txt:1379,搜「A gateway」;:1389,搜「per tool」)。 2 3 4

  16. 出处:「Securing Your Application」第 1359-1361 段(text/13-fm-securing-your-application.txt:1359,搜「Entra ID」)与第 1481-1497 段(text/13-fm-securing-your-application.txt:1485,搜「mcp-for-beginners」)。 2 3