平台无法传请求头时用 nginx 注入鉴权 (Injecting Auth Headers via nginx for Header-Blind Clients)
场景
一个第三方平台(如校园智能体平台)要连你的 MCP 服务,但它的「注册远程服务」表单只有 URL 输入框,没有自定义请求头的地方。
而你的 MCP 服务是 Bearer 鉴权的 —— 不收 Authorization 头就回 401。
症状
在平台里填正确 URL,连接失败:
SSE error: Non-200 status code (401)用 curl 手测同一个 URL 带 Bearer 头却完全正常。说明问题在平台没把头发出去,不是你的服务有问题。
先确认:平台到底支不支持
别急着改服务端。先验证平台是不是真的吞了头字段。
两种确认方式:
- 看前端表单:抓平台页面的 JS chunk,找注册请求的 body 构造。如果只有
name / description / transport / httpUrl四个字段,就是没有。 - 直接打它的 API:手动往注册接口塞
headers/authHeaders字段,再查服务详情 —— 如果字段被丢弃,说明后端也不存。
实测(2026-09,灵犀平台):塞进去的 headers 字段被直接丢掉,返回的对象里根本没有这个键。
解法:让 nginx 把 URL 参数转成请求头
思路是把密钥放在 URL 里(平台支持填任意 URL),由 nginx 取出后转成 Authorization 头转发给后端。
第 1 步:定义 map
放在 nginx 的 http 上下文(nginx.conf.d/maps.conf 之类被 include 在 http 层的文件里):
map $arg_token $mcp_bearer_auth {
default "Bearer $arg_token";
'' $http_authorization;
}语义:
- URL 带
?token=xxx→ 转成Authorization: Bearer xxx - 不带 token → 透传客户端自己带的 Authorization 头
第二行是关键,它保证了向后兼容:老的、能正常带头的客户端不受影响。
第 2 步:在目标路由上引用
location ^~ /your/mcp/path {
proxy_pass http://127.0.0.1:3182;
proxy_set_header Authorization $mcp_bearer_auth;
# ... 其它常规头
}第 3 步:平台里填带 token 的 URL
https://your.domain/your/mcp/path?token=<密钥>验证矩阵(四种都要测)
改完必须把这个矩阵走完,漏测任何一格都可能埋雷:
| 请求 | 预期 | 含义 |
|---|---|---|
?token=<正确> | 200 | 新路通了 |
?token=<错误> | 401 | 密钥确实在起作用,不是形同虚设 |
无 token + Authorization 头 | 200 | 老客户端没被破坏 |
| 无 token 无头 | 401 | 默认仍是拒绝,没变成公开 |
坑:nginx 配置生成器不补分号
如果用的是「routes.json → 生成 nginx 配置」那套(见 platform deploy.sh 首次部署不生效 (Nginx Deploy Copy-Order Bug) 里的 generate-config.cjs),它的 proxy_set_headers 字段是直接拼接的:
for (const header of p.proxy_set_headers || []) lines.push(`proxy_set_header ${header}`);没有补分号。 所以值里必须自己带上:
"proxyOptions": {
"proxy_set_headers": ["Authorization $mcp_bearer_auth;"]
}漏了分号,nginx -t 会报:
unexpected "}" in .../sites/xxx.conf:NNN好消息是规范的 deploy.sh 自带 nginx -t 失败回滚,不会把坏配置留下去。
下次如何识别:生成器报 unexpected "}" 时,先去掉行尾看是不是分号缺失。看生成器源码里是 lines.push(\... ${header}\`) 还是 lines.push(\`... ${header};\`)`。
安全性权衡
- 密钥明文出现在平台的服务配置里 —— 平台管理员能看到。这是把权限交给平台,能接受才这么干。
- 密钥会出现在 nginx 的
$request_uri和访问日志里。如果日志会外发,考虑改用请求头方案或给 MCP 单独发一把只读 key。 - 比「直接在路由上硬编码 Authorization 头」安全:那样等于任何知道 URL 的人都能用,等于取消鉴权。
- 比「改 MCP 服务端让它接受 URL 参数」省事:不动业务代码,不重启服务。
相关概念
- OpenAI 兼容网关的参数透传验证 (Verifying Parameter Passthrough in OpenAI-Compatible Gateways) —— 同一批工作中发现的网关行为
- MCP 远程端点手动探测 (Manually Probing MCP Endpoints)
- platform deploy.sh 首次部署不生效 (Nginx Deploy Copy-Order Bug) —— 同一个
deploy.sh体系
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Network