OAuth代理服务

MCP Janus是一个OAuth 2.1代理服务,为企业级MCP服务器提供安全认证,无需修改服务器代码。

  • by maurik77
  • Go
  • 本地部署
  • 5 星标
  • 更新于
在 GitHub 查看 maurik77/mcp-janus

使用说明

MCP Janus

OAuth 2.1代理,为MCP服务器提供企业级安全性,而无需接触任何服务器代码。


问题

大多数MCP代理以错误的方式解决了身份验证问题:它们从授权服务器接收真实的IdP JWT,并将其直接传递给MCP客户端。客户端现在可以解码该JWT,读取每个声明,根据IdP重放它,并发现您的身份提供者的内部——这直接违反了 MCP授权规范,明确禁止转发不是为代理本身颁发的令牌。

安全影响是真实的:

  • 客户端了解您的IdP URL、租户、受众和用户声明
  • 被盗令牌可用于代理 和 上游IdP
  • “代理令牌”和“其他所有令牌”之间没有界限

Janus做什么

Janus坐在任何MCP服务器前面,代表客户端运行完整的OAuth 2.1+PKCE流程。在与真实IdP交换授权码后 使用AES-256-GCM对IdP JWT进行加密 并给客户端一个不透明的blob。在每个后续请求中,它都会解密、验证并向上游转发真实的JWT——不可见。

客户端永远不会看到、解码或重放真实的令牌。零令牌传递。完全符合MCP规范。

MCP Client                        MCP Janus Proxy                    Upstream MCP Server
    │                                    │                                    │
    │  Authorization: Bearer <opaque>    │                                    │
    │ ──────────────────────────────────>│                                    │
    │                                    │ 1. Decrypt opaque token (AES-GCM)  │
    │                                    │ 2. Validate JWT (exp, aud, iss)    │
    │                                    │ 3. Map claims → HTTP headers       │
    │                                    │                                    │
    │                                    │  Authorization: Bearer <real JWT>  │
    │                                    │  X-Sub: user123                    │
    │                                    │ ──────────────────────────────────>│
    │                                    │                                    │
    │                                    │◄──────────────────────────────────│
    │◄──────────────────────────────────│                                    │

这是给谁的?

  • 平台工程师 在生产环境中部署需要真正安全性而非管道磁带身份验证的MCP服务器
  • 企业团队 将Claude或ChatGPT与IdP背后的内部工具(Azure AD B2C、Okta、Keycloak、Auth0)集成
  • MCP服务器开发人员 他们希望OAuth 2.1合规性,而无需从头开始重新实现身份验证
  • 安保团队 审计AI集成以检测令牌泄漏和规范合规性

主要特点

安全

  • 不透明加密令牌 --AES-256-GCM(AEAD)封装了每个IdP JWT;客户端只能看到密文
  • 无令牌传递 --代理发行自己的令牌,从不向上游转发客户端令牌
  • JWT验证 --通过JWKS密钥提取和自动轮换进行完整的声明验证(到期、受众、发行人)
  • 标题映射声明 --可配置的IdP声明注入上游HTTP标头
  • 加密的客户端凭据 --动态注册返回AEAD加密 client_id / client_secret
  • 自行发行代币模式 --Janus可以为不支持令牌刷新的MCP客户端(如Claude和ChatGPT)颁发自己的长期令牌(可配置TTL)

标准合规

  • OAuth 2.1+PKCE --带有S256码挑战的授权码流;公共客户(否 client_secret)完全支持
  • RFC 7591 --带有完整§3.2.1响应的动态客户端注册
  • RFC 8414 --OAuth 2.0授权服务器元数据(/.well-known/oauth-authorization-server)
  • RFC 9728 --受保护的资源元数据,包括 bearer_methods_supported: ["header"]
  • OpenID连接发现 — /.well-known/openid-configuration
  • RFC 9207 — iss 授权响应中的参数(AS混淆保护)

运营

  • 单个二进制 — go build 生成一个静态二进制文件,零运行时依赖关系
  • 开放遥测 --分布式跟踪和度量(Jaeger、Prometheus、Grafana开箱即用)
  • Docker Compose --一个命令代理+完整的可观察性堆栈
  • 结构化日志记录 --JSON日志,可配置级别
  • 优雅关闭 --SIGTERM上的清洁连接排水
  • CORS支持 --选择加入基于浏览器的MCP客户端(例如MCP Inspector)

快速开始

选项A——使用Keycloak进行本地测试(建议首次运行)

git clone https://github.com/maurik77/mcp-janus.git
cd mcp-janus

# Start Keycloak + MCP test server
docker compose -f docker-compose.keycloak.yaml up -d

# Bootstrap realm, client, and test user — writes .env.keycloak-dev
./scripts/keycloak/setup-keycloak.sh        # Linux/macOS
# .\scripts\keycloak\setup-keycloak.ps1     # Windows (PowerShell)

# Build and run the proxy
task build
cp config.keycloak-dev.yaml config.yaml
source .env.keycloak-dev && CONFIG_PATH=. ./bin/mcpproxy

# Run the full end-to-end test (opens browser for login)
./scripts/keycloak/test-proxy-flow.sh

看 docs/guide_keyclok.md 获取完整的Keycloak设置指南,包括Windows(PowerShell)说明。

选项B——带上自己的身份证

git clone https://github.com/maurik77/mcp-janus.git
cd mcp-janus

go mod download
go build -o bin/mcpproxy ./cmd/proxy

# Edit config.yaml with your IdP's OIDC discovery URL and client credentials
export MCP_IDP_CLIENT_SECRET="your-idp-client-secret"
CONFIG_PATH=. ./bin/mcpproxy

或使用 任务 快捷方式:

task install   # go mod download + verify
task build     # build → ./bin/mcpproxy
task run       # build + run (needs MCP_IDP_CLIENT_SECRET)

验证它是否正在运行

curl http://localhost:8080/health
# OK

curl http://localhost:8080/.well-known/oauth-protected-resource | jq .

运作原理

标准不透明令牌流

  1. 注册 --客户电话 POST /register 使用重定向URI。代理返回加密的AEAD client_id 和 client_secret (RFC 7591§3.2.1)。
  2. 授权 --客户端重定向到 GET /auth 使用PKCE code_challenge.Proxy重定向到真实的IdP。
  3. 回调 --IdP重定向回 GET /callbackProxy接收授权码。
  4. 代币兑换 --客户电话 POST /token 随着 code_verifier代理与IdP进行交换,接收真实的JWT,用AES-256-GCM对其进行加密,并向客户端返回一个不透明的承载。
  5. 经过身份验证的请求 --客户端发送 Authorization: Bearer <opaque> 到 /mcp/*.Proxy解密、验证JWT、将声明映射到标头,并使用真实令牌进行转发。
  6. 刷新 --客户电话 POST /refresh 使用加密的刷新令牌。代理解密,用IdP刷新,重新加密,并返回一个新的不透明承载。

自行发行代币模式(token_behavior: self_issued)

一些MCP客户端(Claude、ChatGPT)只完成一次OAuth流,从不调用 /refresh。默认情况下 proxy 模式下,会话在IdP令牌到期时到期(通常为1小时)。这 self_issued 模式解决了这个问题:

  1. 在初始IdP交换后,验证JWT 一次 并提取索赔。
  2. Janus发行自己的不透明代币,其中包含 加密映射声明 以及Janus控制的到期日(token_ttl).
  3. 在每次后续请求中,代理都会解密令牌,检查到期时间,并将声明作为标头注入-- 没有JWKS呼叫,没有IdP联系人.
  4. 如果 /refresh 如果被调用,则从相同的加密声明中发出新的访问令牌,直到 token_max_ttl 而无需触摸IdP。

权衡:

proxy self_issued
令牌寿命 IdP受控(例如1小时) Janus受控(例如720小时)
IdP撤销在 ~1小时内生效 最多 token_ttl
JWKS按请求调用 是(缓存) 否
声明新鲜度 每次请求 登录时冻结
没有刷新支持的客户端 会话每小时过期 已满 token_ttl 持续时间

令牌加密

  • 算法:AES-256-GCM(AEAD——具有相关数据的认证加密)
  • 过程:真正的JWT→ 用256位主密钥加密→ 每次操作的随机随机数→ base64url编码→ 不透明字符串
  • 解密:提取载体→ base64url解码→ 解密→ 解析JWT→ 验证索赔

索赔映射

IdP JWT声明映射到每个代理请求的上游HTTP标头:

idp:
  claims_mapping:
    sub: X-Sub
    name: X-Full-Name
    email: X-Email
    upn: X-UPN

上游MCP服务器接收干净的HTTP标头——没有JWT解析,没有IdP依赖性。


配置

创建 config.yaml 在工作目录中(或使用 MCP_-前缀环境变量):

proxy:
  base_url: http://localhost:8080        # Canonical URL of this proxy
  listen_addr: ":8080"
  log_level: info                        # trace|debug|info|warn|error
  log_format: json
  cors:
    enabled: false                       # true for browser clients (e.g. MCP Inspector)
    allowed_origins:
      - http://localhost:6274
  token_behavior: proxy                  # proxy (default) | self_issued
  token_ttl: 24h                         # [self_issued] lifetime of each access token
  token_max_ttl: 168h                    # [self_issued] max window from original login

idp:
  client_id: your-idp-client-id
  client_secret: ""                      # use MCP_IDP_CLIENT_SECRET env var
  openid_configuration_url: https://auth.example.com/.well-known/openid-configuration
  scopes:
    - openid
    - profile
    - email
  claims_mapping:
    sub: X-Sub
    name: X-Full-Name
    email: X-Email
  jwt_leeway: 10s

encryption:
  # Generate with: openssl rand -hex 32
  master_key: "your-64-char-hex-key"

upstream:
  name: my-mcp-server
  resource: https://mcp.example.com     # resource indicator for audience binding
  base_url: https://mcp.example.com
  path_prefix: /mcp

telemetry:
  enabled: true
  service_name: mcp-proxy
  otlp_endpoint: localhost:4318

环境变量覆盖:

export MCP_IDP_CLIENT_SECRET="your-secret"
export MCP_PROXY_BASE_URL="https://proxy.example.com"
export MCP_ENCRYPTION_MASTER_KEY="$(openssl rand -hex 32)"
export MCP_PROXY_CORS_ENABLED=true
export MCP_TOKEN_BEHAVIOR=self_issued
export MCP_TOKEN_TTL=720h

看 .env.示例 查看完整列表。


API终点

方法 路径 描述
GET /.well-known/openid-configuration OpenID连接发现
GET /.well-known/oauth-authorization-server 授权服务器元数据(RFC 8414)
GET /.well-known/oauth-protected-resource 受保护的资源元数据(RFC 9728)
POST /register 动态客户端注册(RFC 7591)
GET /auth 使用PKCE进行OAuth授权
GET /callback 来自IdP的OAuth回调
POST /token 授权码→ 不透明载体
POST /refresh 刷新令牌交换
GET/POST /mcp/* 经过身份验证的MCP代理
GET /health 健康检查

示例:注册客户

curl -s -X POST http://localhost:8080/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP Client",
    "redirect_uris": ["http://localhost:3000/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"]
  }' | jq .

看 文档/测试指南.md 对于完整的卷曲序列。


可观测性

docker compose -f docker-compose.observability.yaml up -d

启动Jaeger(跟踪)、Prometheus(指标)、Grafana(仪表板)和OpenTetry收集器。代理在以下情况下自动导出 telemetry.enabled: true.

关键指标:

度量 描述
mcp.proxy.auth.requests.total 按结果验证请求
mcp.proxy.token.exchange.duration 令牌交换延迟
mcp.proxy.requests.total 按方法/路径/状态列出的代理请求
mcp.proxy.upstream.errors.total 上游错误计数器

看 docs/opentelemetry.md 用于仪表板设置。


码头工人

# Proxy + MCP test server
docker compose up -d

# Full observability stack
docker compose -f docker-compose.observability.yaml up -d

# Both together
docker compose -f docker-compose.yaml -f docker-compose.observability.yaml up -d

# Keycloak dev environment
docker compose -f docker-compose.keycloak.yaml up -d

部署

这 deploy.sh 通过Helm一步完成脚本构建、标记、推送和部署:

export REGISTRY=myregistry.azurecr.io
./deploy.sh 1.0.0

执行的步骤: docker build → 标签+推送到注册表→ 更新 deployment/values-dev.yaml → helm upgrade.


贡献

  1. 分叉仓库并创建功能分支
  2. 跑 task fmt 在承诺之前
  3. 添加新功能测试(首选表驱动)
  4. 确保 task test 和 task lint 通过
  5. 打开一个带有清晰描述的拉取请求

如果你正在测试一个真正的IdP 钥匙斗篷设置指南 在5分钟内为您提供本地IdP。


参考文献

MCP规格

OAuth标准

项目文档