本文系统讲解 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 工作原理

  1. 客户端发现
    客户端请求 /.well-known/oauth-protected-resource,获取 IdP 地址。
  2. 动态注册
    客户端调用 /register,自动获得 OAuth 凭据(无需你在 GitHub 控制台手动创建 App)。
  3. 自动授权
    用户在 IdP 登录后,令牌自动颁发给客户端。
  4. 令牌验证
    FastMCP 验证 JWT 签名、issaud,提取权限。

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 流程(代理模式)

User GitHub OAuth FastMCP 代理 MCP 客户端 User GitHub OAuth FastMCP 代理 MCP 客户端 /register (带动态回调 URL) 返回固定凭据 + 记录回调 URL /authorize 重定向到 GitHub (用固定回调) 登录 + 同意 授权 重定向到 /auth/callback + code 用 code 换 token 重定向到客户端原始回调 URL + 新 code 用新 code 换 FastMCP JWT 返回 JWT(含 GitHub token)

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 服务器 极高(不推荐)
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐