返回 AICAP-180
B7 · Day 61OAuth 2.1 + MCP 安全 + CI gate

OAuth 2.1 资源服务器模型 (RFC 9728/8707)

B1→B6 我们把一个进程内的 MCP 工具注册表升级成了真网络 MCP server(B6,2026-07-28 stateless spec 形状):真 JSON-RPC、真 tools/list/tools/call、真 schema 校验。一个能被远端 agent 通过 HTTP 调用的 server,下一道必答题就是授权——谁有资格调哪个工具。B7 整批就是补这道课:从 OAuth 2.

阶段: B7 · OAuth 2.1 + MCP 安全 + CI gate(Day 61-70) 标签: #oauth21 #mcp-security #resource-server #confused-deputy

今日导引(由浅入深)

B1→B6 我们把一个进程内的 MCP 工具注册表升级成了网络 MCP server(B6,2026-07-28 stateless spec 形状):真 JSON-RPC、真 tools/list/tools/call、真 schema 校验。一个能被远端 agent 通过 HTTP 调用的 server,下一道必答题就是授权——谁有资格调哪个工具。B7 整批就是补这道课:从 OAuth 2.1 的角色模型(今天)→ 三类 MCP 攻击面(D62)→ JWT 校验内核(D63)→ 事中拦截点位(D64)→ 拒绝/成功语义(D65-66)→ CI gate(D67-70)。今天的「最小可判定产出」:一张 5 工具 × scope 矩阵 + 一份 OAuth 威胁清单,把「MCP server = OAuth resource server」这个身份钉死。

1. 机理精读

MCP server 在 OAuth 2.1 里是 resource server(RS),不是 authorization server(AS)。 这是整批的地基。RS 的职责很窄:它不签发 token,只校验别人(AS)签发的 access token,命中自己才放行。把签发和校验分开,是 OAuth 全套设计的第一性原理——RS 不需要持有用户凭据,攻击 RS 拿不到签发能力。

为什么需要两条「防混淆」约束? 三方流的天然漏洞是 confused deputy(被混淆的代理):一个 token 被签出来后,如果它没绑定「该去打谁」,持有者就能把它转去打另一个本不该接受它的服务,而那个服务傻乎乎地信了。OAuth 2.1 用两条 RFC 堵这个洞:

  • RFC 9728 — Protected Resource Metadata(2025-04):让 client 通过 /.well-known/oauth-protected-resource 发现这个 RS 接受哪个 AS、期望什么 audience。客户端不再靠带外约定或猜测,而是从 RS 自己暴露的元数据里读出「去哪个 AS 取 token、token 的 aud 该填什么」。
  • RFC 8707 — Resource Indicators:让 client 在向 AS 取 token 时用 resource 参数声明「我要拿去打这个 RS」,AS 据此把 token 的 aud(audience)限定到具体 RS。于是一个为 RS-A 签的 token,aud=RS-A,拿去打 RS-B 时 RS-B 一验 aud 不是自己就拒——confused deputy 被堵死在校验层。

三方流的完整链路(逐步拆开,这是后面所有天的心智模型):

  1. 发现:client 访问 RS 的 /.well-known/oauth-protected-resource(RFC 9728),读到「该 RS 信任哪个 AS、期望什么 aud」。
  2. 取 token:client 带 PKCE 走 OAuth 2.1 授权码流到 AS,并按 RFC 8707 用 resource=<RS 标识> 声明用途。
  3. 签发:AS 校验 client + 用户授权后,签一个 aud=<RS> 的 audience-restricted access token。
  4. 携带:client 带 Authorization: Bearer <token> 调 RS。
  5. 校验:RS 验签名 + iss + exp/nbf + aud 命中自己 + scope 覆盖目标工具,全过才派发工具。

注意第 5 步里 aud 校验是这条链最容易被省略、却最关键的一环——省了它,RFC 8707 的 audience restriction 就形同虚设,confused deputy 立刻复活。

与相邻概念的边界:RFC 9728 解决「发现」(client 怎么知道去哪、填什么),RFC 8707 解决「绑定」(token 绑到哪个 RS),二者配合才闭环。这跟传统「API gateway 入口鉴权一次」不同——OAuth 的 audience 模型是为多 RS、跨服务的零信任拓扑设计的,每个 RS 独立校验自己的 aud,没有「内网即可信」的假设(这一点 D64 会展开到执行面)。

为什么 OAuth 2.1 而非 OAuth 2.0? OAuth 2.1 是把散落在十几份 RFC 与 BCP 里的安全最佳实践收敛成单一规范:强制 PKCE(即便机密 client)、移除危险的 implicit grant 与 ROPC(resource owner password credentials)、强制精确 redirect URI 匹配。MCP 选 OAuth 2.1 而非裸 2.0,正是要默认拿到这些纵深防御,而不是让每个 server 实现者自己拼 RFC。

2. 推导 / 手算 / 代码走读

本仓 src/agent/mcp/auth.ts 是这套 RS 原语的本地落地(HS256 对称密钥版,便于无网络确定性测试;生产形态是 RS256 + JWKS)。走读真实符号:

  • mintToken(secret, { sub, scopes, ttlSec?, issuer?, audience? }):用 jose 的 SignJWT 签一个 HS256 token,setSubject(sub)setIssuedAtsetExpirationTime(now + ttlSec)仅当传了 issuer/audiencesetIssuer/setAudience——这对应 RFC 8707 的 audience 绑定是可选启用。注释明确写了 ttlSec 可为负数以铸造过期 token(D63 测过期路径会用到)。
  • verifyAccessToken(token, { secret, issuer?, audience? }):jose 的 jwtVerify 一次性验签名 + iss + aud + exp/nbfalgorithms: ['HS256'] 显式 pin 算法,防 alg-confusion 降级攻击(注释写 "defense-in-depth against alg confusion")。
  • tokenScopes(claims) / requireScope(claims, scope):scope 以空格分隔(OAuth 惯例),requireScope 缺 scope 时抛 insufficient_scope: requires "<scope>"——这正是 OAuth insufficient_scope 错误的语义(D65 会接到 403)。
  • bearerFromHeader(header):正则 ^Bearer\s+(.+)$/i 大小写不敏感地从 Authorization 头抽 token,非 Bearer(如 Basic)返回 null

手算一个 5 工具 × scope 矩阵(今日设计产出)。 对照 AML Copilot 的 5 个受保护工具,按最小权限给每个工具配最小 scope(每个工具只解锁完成它本身所需的权限,不多给):

工具动作最小 scope敏感度
evidence.fetch拉取交易/KYC 证据aml:read读,含 PII
typology.match比对洗钱类型学aml:read
sar.draft起草 SAR 报告aml:write写,生成可疑活动报告
audit.write写审计行audit:write写,不可篡改轨迹
case.read读案件上下文case:read

判定规则:读类工具拿 *:read,写类工具拿 *:write,审计独立成 audit:write(写审计 ≠ 写业务,权限要分桶)。一个只需要 evidence.fetch+typology.match 的初筛 agent,注入的 token 只含 aml:read——它拿不动 sar.draft,因为没有 aml:write。这就是 D65/66 要端到端验证的「scope 收敛」。

scope 命名约定:用 <资源域>:<动作>(如 aml:readaudit:write)而非 read_evidence 这种与单个工具一一绑死的命名——前者让一个 scope 可覆盖同域多工具(aml:read 同时解锁 evidence.fetch + typology.match),既最小又不碎。粒度太细(每工具一个 scope)会让 token 膨胀且授权难管;太粗(一个 aml:*)又违反最小权限。域:动作 是经验上的平衡点。注意本仓 server.ts 当前实际用的是更粗的单 scope mcp:call 门控整个 server(D64 会说明),per-工具的 域:动作 矩阵是 D61 的设计目标,尚未落到 server 执行面。

诚实标注:本仓真 MCP server(src/agent/mcp/tools.ts)当前实际暴露的是 bpe_report / paired_bootstrap / assess_structuring 三个无 key 纯函数工具;上表的 5 个 AML 工具是按 AML Copilot 叙事做的 scope 设计练习(威胁建模),尚未在 tools.ts 注册,不要把它当成已部署工具。

3. 今日实战

  1. 起草 OAuth 威胁清单:在 src/agent/mcp/auth.ts 旁新增 docs/aipa/day61-oauth21-rs.md,列出 RS 视角的威胁项,每项注明缓解原语对应 auth.ts 的哪个函数:

    威胁缓解对应 auth.ts 原语
    无 token入口拒bearerFromHeader 返回 null → 401
    aud(confused deputy / passthrough)audience 校验verifyAccessToken({ audience })
    过期 tokenexp 校验jwtVerify(内含 exp/nbf)
    篡改/伪造签名校验 + alg pinverifyAccessTokenalgorithms:['HS256']
    scope 不足调用前 scope 校验requireScope(claims, scope)
  2. 对照 src/agent/mcp/toolRegistry.ts(进程内教学装置)与 AML Copilot 叙事,列出 5 个受保护工具的最小 scope,做成上面的 5×scope 矩阵。

  3. 把矩阵 + 威胁清单一起提交,作为 D62(写红测试桩)的契约输入。

4. 今日实测 / 产出

  • 提交 docs/aipa/day61-oauth21-rs.md + scope 矩阵(5 工具 × scope)。已完成(文档/设计产出)
  • auth.ts 已落地 mint / verify / scope 三原语(HS256-pinned),本地自签 token 校验路径已通。已完成
  • JWKS / RS256 audience 校验为生产形态,本地 demo 用对称密钥。待建(生产迁移项)——不要把它写成「已完成」。

5. 常见误区 / 陷阱

  1. 把 RS 当 AS:MCP server 不该自己签 token;它只验。混了角色就会把签发密钥暴露在边缘服务上。
  2. 省略 aud 校验:只验签名+过期、不验 audience,等于把 RFC 8707 的 audience restriction 作废,confused deputy 直接复活。
  3. scope 配太宽:给一个初筛 agent 配 aml:write+audit:write 这种全家桶,违反最小权限——D65/66 的反例就源于此。
  4. HS256 当生产方案:对称密钥「可签即可验」,无法把校验权下放给只持公钥的 RS,也不支持 kid 轮换;本仓明确只用于本地/测试。

6. 学习资源(每条带 YYYY-MM)

  • RFC 9728 OAuth 2.0 Protected Resource Metadata(IETF,2025-04)— RS 元数据发现端点。
  • RFC 8707 Resource Indicators for OAuth 2.0(IETF,2020-02,现行有效)— audience 限定。
  • RFC 6750 OAuth 2.0 Bearer Token Usage(IETF,2012-10)— Bearer + WWW-Authenticate 语义(D65 用)。
  • OAuth 2.1 draft(IETF oauth-v2-1,持续更新至 2025)— 把多份 RFC 收敛成单一最佳实践。
  • MCP Authorization spec(modelcontextprotocol.io,2026-07-28 定稿在即)— 把 RS 元数据发现纳入 MCP。
  • 本仓代码:src/agent/mcp/auth.ts(2026-06,HS256 RS 原语)。

SOTA检查 (2026-06 更新)

  • 当前主流:OAuth 2.1(RFC 9728 + 8707 + 6750 + PKCE)是 MCP 授权的事实标准;RFC 9728(2025-04)与 RFC 8707 现行有效。
  • 是否仍 SOTA:是。MCP 2026-07-28 spec 将正式把 RS 元数据发现纳入——该规范仍在 finalize,server 端的强制 metadata 端点构建必须排在 07-28 之后(B7 后续天若涉及 metadata 端点,执行当周需复查规范定稿状态)。
  • 过时黑名单:禁止沿用「MCP 无授权层」旧叙事——2025 早期 spec 没有 OAuth 已过时;不要用 implicit grant(OAuth 2.1 已移除);不要把 HS256 对称密钥当生产 RS 方案。
  • 下次复查点:2026-07-28 MCP spec 定稿当周,核对 RS metadata 是否从 SHOULD 升级为 MUST。

衔接

  • 昨天:Day 60(B6 收口 · 真 MCP server 的 stateless 协议形状)
  • 今天:MCP server = OAuth resource server,靠 RFC 9728 发现 + RFC 8707 audience 绑定堵 confused deputy;产出 5×scope 矩阵。
  • 明天:Day 62 — MCP Security Best Practices 2026-01(把威胁清单变成 6 条红测试断言)。