FastMCP 2.0 服务端用户权限与身份验证教学文档(权限篇)
本文系统介绍了FastMCP 2.0的身份验证与权限控制体系,涵盖四种主要模式:1)简单的令牌验证,适用于已有JWT基础设施的场景;2)支持动态客户端注册(DCR)的远程OAuth,适合现代身份提供商;3)OAuth代理模式,用于集成GitHub等传统OAuth服务;4)完整的OAuth服务器方案。文章重点分析了各模式的技术复杂度、适用场景和实现方法,推荐企业用户优先选择支持DCR的远程OAuth
本文系统讲解 FastMCP 2.0 的用户身份验证(Authentication) 与 权限控制(Authorization) 体系,涵盖从简单令牌验证到完整 OAuth 服务器的四种身份验证模式,帮助开发者构建安全、合规、生产就绪的 MCP 服务端。
一、FastMCP 权限模型总览
FastMCP 的权限体系围绕 MCP 协议(Model Context Protocol)展开。MCP 本身不强制要求身份验证,但支持将 MCP 服务端置于安全边界后,由 LLM 客户端提供身份证明(以令牌形式)。
1.1 MCP 的权限特点
- 无内置登录流程:MCP 是无头协议,不假设有浏览器用户,所有交互由客户端程序发起。
- 令牌驱动:权限基于 Bearer Token,由客户端在请求头中携带。
- 客户端主导发现:客户端通过检查
/.well-known/...端点自动发现服务端的身份验证要求(是否需要?用哪家 OAuth 提供商?)。 - 权限声明嵌入令牌:用户角色、权限、作用域等信息通常编码在 JWT 的
claims中。
1.2 FastMCP 四种身份验证模式
| 模式 | 适用场景 | 技术复杂度 | 是否符合 MCP 自动化认证规范 |
|---|---|---|---|
| 1. 令牌验证(Token Verification) | 内部微服务、已有 JWT 基础设施 | ★☆☆☆☆(低) | ❌(需外部提供令牌) |
| 2. 远程 OAuth(Remote OAuth / DCR) | 支持动态客户端注册(DCR)的现代 IdP(如 WorkOS AuthKit, Descope) | ★★★☆☆(中) | ✅(全自动) |
| 3. OAuth 代理(OAuth Proxy) | 不支持 DCR 的传统 OAuth 提供商(GitHub, Google, Azure 等) | ★★★★☆(高) | ✅(通过代理模拟 DCR) |
| 4. 完整 OAuth 服务器(Full OAuth Server) | 空隙环境、自研 IdP、特殊合规要求 | ★★★★★(极高) | ✅ |
📌 推荐路径:
- 企业用户优先选择 Remote OAuth(若 IdP 支持 DCR)
- 集成 GitHub/Google 等选择 OAuth 代理
- 内部系统用 令牌验证
- 避免 自建 OAuth 服务器,除非别无选择
二、模式一:令牌验证(Token Verification)
适用于已有 JWT 基础设施的场景(如 API 网关、微服务架构)。
2.1 核心类:JWTVerifier
from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier
# 方案1:通过 JWKS 自动轮换公钥(推荐用于生产)
verifier = JWTVerifier(
jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json",
issuer="https://auth.yourcompany.com", # 必须匹配令牌中的 iss
audience="mcp-production-api", # 必须匹配令牌中的 aud
algorithm="RS256" # 默认 RS256
)
# 方案2:对称密钥(HMAC,用于内部可信系统)
verifier = JWTVerifier(
public_key="your-32-char-shared-secret", # 注意:此处传的是对称密钥
issuer="internal-auth",
audience="mcp-internal",
algorithm="HS256"
)
# 创建受保护的 MCP 服务器
mcp = FastMCP(name="ProtectedServer", auth=verifier)
2.2 权限控制:从令牌声明中提取信息
在工具中通过 get_access_token() 获取令牌内容:
from fastmcp import Context
from fastmcp.server.dependencies import get_access_token
@mcp.tool
async def get_user_data(ctx: Context) -> dict:
# 获取令牌信息
token = get_access_token()
# 令牌声明(JWT payload)
claims = token.claims
user_id = claims.get("sub") # 用户ID
roles = claims.get("roles", []) # 角色列表
scopes = claims.get("scope", "").split() # OAuth 作用域
# 基于角色的权限控制
if "admin" not in roles:
raise ValueError("需要管理员权限")
return {"user_id": user_id, "data": "敏感数据"}
2.3 使用场景与限制
- ✅ 优点:简单、轻量、与现有系统无缝集成
- ❌ 缺点:客户端必须预先获取令牌,无自动发现机制
- 🚫 不适用于面向终端用户的 MCP 服务(用户无法手动提供令牌)
三、模式二:远程 OAuth(Remote OAuth)— 支持 DCR
适用于支持 **Dynamic Client Registration **(DCR) 的身份提供商(如 WorkOS AuthKit, Descope)。
3.1 核心类:RemoteAuthProvider
from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import AuthKitProvider
# 使用 WorkOS AuthKit(内置支持 DCR)
auth = AuthKitProvider(
workos_api_key="...",
workos_project_id="...",
base_url="https://your-mcp-server.com" # 你的服务器公网 URL
)
mcp = FastMCP(name="DCRServer", auth=auth)
3.2 工作原理
- 客户端发现:
客户端请求/.well-known/oauth-protected-resource,获取 IdP 地址。 - 动态注册:
客户端调用/register,自动获得 OAuth 凭据(无需你在 GitHub 控制台手动创建 App)。 - 自动授权:
用户在 IdP 登录后,令牌自动颁发给客户端。 - 令牌验证:
FastMCP 验证 JWT 签名、iss、aud,提取权限。
3.3 权限控制
与令牌验证模式相同,通过 get_access_token() 获取声明:
@mcp.tool
def admin_only_tool(ctx: Context) -> str:
token = get_access_token()
if "admin" not in token.claims.get("roles", []):
raise ValueError("权限不足")
return "执行管理员操作"
3.4 适用 IdP 列表
| 身份提供商 | DCR 支持 | FastMCP 内置类 |
|---|---|---|
| WorkOS AuthKit | ✅ | AuthKitProvider |
| Descope | ✅ | 需自定义 RemoteAuthProvider |
| Auth0 / Google / Azure | ❌ | 不适用(应使用 OAuth 代理) |
四、模式三:OAuth 代理(OAuth Proxy)— 兼容传统 OAuth
最常用模式,用于集成 GitHub、Google、Azure 等不支持 DCR 的提供商。
4.1 核心类:OAuthProxy 及其子类
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# 生产环境配置(必须加密存储 + 固定密钥)
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
base_url="https://your-server.com", # 你的服务器公网 URL
jwt_signing_key=os.environ["JWT_SIGNING_KEY"], # 用于签发 FastMCP JWT
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(host="redis.example.com"),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
)
)
mcp = FastMCP(name="GitHubAuthServer", auth=auth)
4.2 OAuth 流程(代理模式)
4.3 安全机制
- 双层 PKCE:代理对客户端和上游提供商分别验证 PKCE
- 令牌工厂模式:
- 上游令牌(GitHub Token) → 加密存储于 Redis
- 客户端拿到的是 FastMCP 签发的 JWT(HS256)
- 混乱代理攻击防护:首次连接时显示同意页面,需用户手动批准
4.4 权限控制
GitHub 不返回角色,但可从用户信息推断:
@mcp.tool
async def require_org_member(ctx: Context) -> str:
token = get_access_token()
github_token = token.claims["upstream_token"] # 获取原始 GitHub Token
# 调用 GitHub API 检查组织成员身份
async with httpx.AsyncClient() as client:
resp = await client.get(
"https://api.github.com/user/memberships/orgs",
headers={"Authorization": f"token {github_token}"}
)
orgs = [org["organization"]["login"] for org in resp.json()]
if "my-company" not in orgs:
raise ValueError("必须是 my-company 组织成员")
return "访问授权"
五、模式四:完整 OAuth 服务器(不推荐)
仅用于特殊场景(如军事、金融合规要求)。
5.1 核心类:OAuthProvider(抽象基类)
from fastmcp.server.auth.providers.oauth import OAuthProvider
from fastmcp.server.auth.models import OAuthClientInformationFull, AccessToken
class MyOAuthProvider(OAuthProvider):
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
# 从数据库查询客户端
return await db.get_client(client_id)
async def store_client(self, client: OAuthClientInformationFull) -> None:
# 保存新注册的客户端
await db.save_client(client)
async def handle_authorization(
self, client: OAuthClientInformationFull, ...
) -> str: # 返回重定向 URL
# 实现登录页面 + 同意逻辑(需自建 HTML)
...
async def exchange_authorization_code(self, ...) -> tuple[AccessToken, RefreshToken]:
# 验证 code 并颁发令牌
...
async def validate_access_token(self, token: str) -> AccessToken | None:
# 验证令牌有效性
...
5.2 为什么避免使用?
- 需实现完整的 OAuth 2.1 安全特性(PKCE、状态、重定向验证等)
- 需维护用户凭证存储(密码哈希、MFA)
- 需处理令牌刷新、撤销、轮换
- 需持续应对新型攻击(如混乱代理、令牌劫持)
✅ 替代方案:使用 Auth0 / Okta / Azure AD 等成熟 IdP + OAuth 代理
六、权限控制最佳实践
6.1 声明 vs. 作用域
| 机制 | 位置 | 用途 | 示例 |
|---|---|---|---|
| JWT Claims | token.claims |
用户身份、角色、属性 | {"roles": ["admin"], "dept": "eng"} |
| OAuth Scopes | token.claims["scope"] |
细粒度 API 权限 | "read:profile write:files" |
6.2 工具级权限装饰器(推荐)
from functools import wraps
def require_role(*roles):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
# 从上下文或依赖中提取 token
token = get_access_token()
user_roles = set(token.claims.get("roles", []))
if not user_roles.intersection(roles):
raise ValueError(f"需要角色: {roles}")
return await func(*args, **kwargs)
return wrapper
return decorator
@mcp.tool
@require_role("admin", "ops")
async def delete_user(user_id: str) -> str:
return f"用户 {user_id} 已删除"
6.3 资源级权限控制
@mcp.resource("//user/{user_id}")
async def get_user_resource(user_id: str, ctx: Context) -> dict:
token = get_access_token()
requester_id = token.claims["sub"]
# 仅允许查看自己或管理员
if requester_id != user_id and "admin" not in token.claims.get("roles", []):
raise ResourceError("无权访问该用户数据")
return {"id": user_id, "name": "Alice"}
七、生产部署安全要点
7.1 必须配置项(生产环境)
| 组件 | 配置项 | 说明 |
|---|---|---|
| OAuth 代理 | jwt_signing_key |
固定密钥,避免重启后客户端失效 |
| OAuth 代理 | client_storage |
Redis/DynamoDB + FernetEncryptionWrapper |
| 所有模式 | mask_error_details=True |
防止泄露内部错误 |
| 服务器 | HTTPS + CORS 限制 | 确保传输安全 |
7.2 密钥管理
# 生成强密钥(32字节)
openssl rand -base64 32
# 示例输出: kX9p2sLmNvQwRtYzA1bC3dE5fG7hJ8kLmNpQrStUvWxYzA1bC3dE5fG7hJ8
7.3 审计日志
- 记录所有
call_tool请求(含client_id,user_id, 参数) - 使用
ctx.info()发送关键操作日志给客户端(可选)
八、总结:如何选择身份验证模式?
| 你的场景 | 推荐模式 | 配置复杂度 |
|---|---|---|
| 内部微服务,已有 Auth0/JWT | 令牌验证 | 低 |
| 使用 WorkOS / Descope | **远程 OAuth **(DCR) | 中 |
| 集成 GitHub / Google / Azure | OAuth 代理 | 中高(需配置存储加密) |
| 需要用户手动注册/登录 | OAuth 代理(配合 IdP) | 中高 |
| 空隙环境,无外部 IdP | 完整 OAuth 服务器 | 极高(不推荐) |
更多推荐
所有评论(0)