使用说明
安全MCP演示-OAuth受众验证
一个最小的可重复示例,演示了根据RFC 8707在MCP(模型上下文协议)服务器中进行正确的OAuth受众验证。
问题
一些MCP客户端无法发送 resource 或 audience 请求OAuth令牌时的参数,违反了RFC 8707和MCP规范,破坏了受众限制访问令牌。
应该发生什么(符合RFC 8707)
- MCP服务器通告:
"resource": "https://example.com/google-drive-mcp" - 客户端在授权请求中包含资源:
resource=https://example.com/google-drive-mcp - 客户端在令牌请求中包含资源:
resource=https://example.com/google-drive-mcp - 令牌的发放与匹配
aud声称 - 令牌验证成功✅
发生了什么(风帆虫)
- MCP服务器通告:
"resource": "https://example.com/google-drive-mcp" - 客户端 省略资源 在授权请求中❌
- 客户端 忽略资源和受众 在令牌请求中❌
- 令牌由通用受众(仅限来源)发行
- 令牌验证失败,因为
aud声明与MCP服务器的资源URI不匹配
为什么这很重要
每 RFC 9728第7.4节,受众限制访问令牌可防止为一个资源服务器颁发的令牌在另一个服务器上使用。如果没有适当的受众验证,一个MCP服务器的令牌可能会在同一主机上的不同MCP服务器上重复使用。
真实世界测试结果
此演示已在多个MCP客户端上进行了测试。以下是实际结果:
✅ 游标-符合RFC 8707标准
[OAuth Server] Authorization request received:
Resource: http://localhost:3000/google-drive-mcp ✅
[OAuth Server] Token request received:
Requested Resource: http://localhost:3000/google-drive-mcp ✅
[MCP Server] Token validation:
Expected audience: http://localhost:3000/google-drive-mcp
Actual audience: http://localhost:3000/google-drive-mcp
✅ VALIDATION SUCCESSFUL
结果:游标成功连接并可以使用所有MCP功能。
❌ Windsurf-违反RFC 8707
[OAuth Server] Authorization request received:
Resource: undefined ❌ NOT SENT
[OAuth Server] Token request received:
Requested Audience: NOT PROVIDED ❌
Requested Resource: NOT PROVIDED ❌
[MCP Server] Token validation:
Expected audience: http://localhost:3000/google-drive-mcp
Actual audience: http://localhost:3000
❌ VALIDATION FAILED: Audience mismatch!
结果:Windsurf无法连接。令牌验证失败,因为没有发送资源参数。
规格违规(Windsurf)
- 授权请求中缺少资源 -违反MCP规范第2.5.1节
- 令牌请求中缺少资源/受众 -违反RFC 8707第2节
- 发现端点错误 请求:
/.well-known/oauth-protected-resource而不是/.well-known/oauth-protected-resource/google-drive-mcp
安装
npm install
用法
启动演示服务器
npm install
npm run dev
这将启动一个包含以下内容的组合服务器:
- 模拟OAuth授权服务器 -向请求的受众发放JWT令牌
- 模拟MCP服务器 -使用OAuth验证实现MCP JSON-RPC协议
使用自动脚本进行快速测试
./test.sh
此脚本演示:
- ✅ 具有正确受众的令牌(
http://localhost:3000/google-drive-mcp) → 请求成功 - ❌ 已剥离路径的令牌(
http://localhost:3000) → 请求失败,受众不匹配
使用真实MCP客户端进行测试
该服务器实现了一个合适的MCP服务器,可以用真实的MCP客户端(Claude Code、Cursor、Windsurf等)进行测试。
将此配置添加到MCP客户端设置中:
{
"mcpServers": {
"secure-mcp-demo": {
"url": "http://localhost:3000/google-drive-mcp",
"transport": "http"
}
}
}
当MCP客户端连接时,它将:
- 尝试在没有令牌的情况下调用MCP服务器
- 收到401回复
WWW-Authenticate包含资源URI的标头 - 从以下位置发现OAuth元数据
.well-known/oauth-protected-resource - 使用以下命令从OAuth服务器请求令牌
audience参数 - 使用令牌重试MCP请求
查看服务器日志 看看每个客户都在做什么。服务器记录每个请求,并提供有关OAuth参数的详细信息,从而可以轻松识别规范合规性问题。
相关规范
MCP规范
MCP客户端必须实现RFC 8707中定义的OAuth 2.0的资源指示符…资源参数必须使用RFC 8707第2节中定义的MCP服务器的规范URI。
RFC 8707-OAuth 2.0的资源指示器
“resource”参数的值必须是绝对URI。
规范要求使用 完整规范URI 作为资源标识符。
RFC 9728-OAuth 2.0安全最佳实践
如果客户端希望与多个资源服务器交互,则应使用[RFC8707]请求受众受限访问令牌。
项目结构
secure-mcp/
├── src/
│ └── server.ts # Combined OAuth + MCP server
├── package.json
├── tsconfig.json
└── README.md
运作原理
- MCP客户端发出初始请求 无需身份验证
/google-drive-mcp - 服务器返回401 包括
WWW-Authenticate: Bearer resource="http://localhost:3000/google-drive-mcp" - 客户端发现OAuth元数据 从
/.well-known/oauth-protected-resource/google-drive-mcp - 客户端发现OAuth服务器 从
/.well-known/oauth-authorization-server - 客户端动态注册 (可选,RFC 7591)通过
/register端点 - 客户端发起授权 随着
resource授权请求中的参数 - 客户端请求令牌 通过OAuth服务器
resource或audience参数- ✅ 兼容客户端(游标):发送
resource=http://localhost:3000/google-drive-mcp - ❌ 不符合要求的客户端(Windsurf):两者均省略
resource和audience参数
- ✅ 兼容客户端(游标):发送
- OAuth服务器发出令牌 与要求的观众在
aud声称 - 客户端重试MCP请求 随着
Authorization: Bearer <token> - 服务器验证令牌 并检查是否
aud声明与资源URI完全匹配- ✅ 如果匹配:请求成功
- ❌ 如果不匹配:返回403,并显示详细的错误消息
已实现OAuth功能
- ✅ 动态客户端注册(RFC 7591) -自动客户端注册
- ✅ 资源指示器(RFC 8707) -受众限制令牌
- ✅ PKCE(RFC 7636) -代码交换支持的证明密钥
- ✅ 授权码流 -完整的OAuth 2.1流程
- ✅ JWT访问令牌(RFC 9068) -结构化访问令牌
at+jwt类型 - ✅ OAuth发现(RFC 8414) -
.well-known/oauth-authorization-server - ✅ 受保护资源元数据 -
.well-known/oauth-protected-resource根据MCP规范
许可证
阿帕奇-2.0
