11.4 客户端凭证与身份断言扩展 本节摘要:本节讲 OAuth 的两个特殊场景扩展。一是客户端凭证(Client Credentials)——机器到机器的认证,无用户参与,适用于服务间调用、定时任务、后台编排。二是身份断言(Identity Assertion)——把一个已有的外部身份(如公司 SSO、第三方登录)带进 MCP,避免用户重复登录。两者都解决了「标准授权码流需要用户参与」的局限。读完本节,你能在无用户场景与外部 IdP 场景下正确接入 MCP。 一、标准授权码流的局限 第 11.3 节的授权码流,有个隐含前提——有用户在浏览器里操作。但有两类场景没有用户: 这两个场景,授权码流都搞不定。SDK 提供两个扩展解决——客户端凭证(场景一)与身份断言(场景二)。
本节摘要:本节讲 OAuth 的两个特殊场景扩展。一是客户端凭证(Client Credentials)——机器到机器的认证,无用户参与,适用于服务间调用、定时任务、后台编排。二是身份断言(Identity Assertion)——把一个已有的外部身份(如公司 SSO、第三方登录)带进 MCP,避免用户重复登录。两者都解决了「标准授权码流需要用户参与」的局限。读完本节,你能在无用户场景与外部 IdP 场景下正确接入 MCP。
第 11.3 节的授权码流,有个隐含前提——有用户在浏览器里操作。但有两类场景没有用户:
场景一:机器到机器 服务 A → 调用 → MCP 服务 B → 没有用户浏览器,授权码流跑不起来 场景二:已有外部身份 用户已在公司 SSO 登录 → 不想在 MCP 里再登录一次 → 但 MCP 不知道用户的 SSO 身份
这两个场景,授权码流都搞不定。SDK 提供两个扩展解决——客户端凭证(场景一)与身份断言(场景二)。
客户端凭证(Client Credentials) 是 OAuth 的一种授权方式,不需要用户参与——客户端用自己的凭证直接换令牌:
标准授权码流(需要用户): 客户端 → 用户同意 → 授权码 → 令牌 (用户是关键) 客户端凭证流(无用户): 客户端(用自己的 ID+密钥)→ 直接换令牌 (无用户参与)
适用场景:
| 场景 | 例子 |
|---|---|
| 服务间调用 | 后端服务 A 调 MCP 服务 B |
| 定时任务 | 后台脚本定期同步数据 |
| 自动化编排 | CI/CD 流水线调 MCP 工具 |
| 无头客户端 | 无 UI 的命令行工具 |
SDK 提供客户端凭证扩展,客户端用 client_credentials 流获取令牌:
from mcp.client.auth.extensions import ClientCredentialsProvider # 配置客户端凭证 provider = ClientCredentialsProvider( token_url="https://mcp.example.com/token", client_id="my_service", # 服务注册的 client_id client_secret="...", # 服务密钥 scopes=["tools:read", "tools:call"], # 需要的权限 ) async with Client( "http://mcp.example.com/mcp", auth=provider, ) as client: # SDK 用 client_credentials 流获取令牌(无用户) result = await client.call_tool("get_data", {...})
客户端凭证流的关键:
💡 技巧:客户端凭证适合「服务身份」而非「用户身份」。客户端凭证拿到的令牌,代表的是「调用方服务」(如
my_service),不是某个用户。所以服务端的权限控制要基于「服务身份」,而非用户身份。如果你的逻辑需要「是哪个用户」,客户端凭证不合适,要用标准授权码流或身份断言。
服务端要支持客户端凭证,需要:
client_credentials 授权类型注册这部分在服务端的存储实现里配置(回顾第 11.2 节的存储接口)。支持客户端凭证的服务端,会在 /token 端点接受 grant_type=client_credentials 的请求。
身份断言(Identity Assertion) 解决「已有外部身份,不想重复登录」的场景。它让你把一个已有的身份(如 SSO、第三方登录)断言给 MCP,换得一个 MCP 令牌:
场景:用户已在公司 SSO 登录 用户 → 公司 SSO 登录 → 拿到 SSO 令牌 │ ▼ 想用 MCP 服务,不想再登录 客户端把 SSO 令牌作为「身份断言」给 MCP │ ▼ MCP 验证断言 MCP 信任 SSO,接受断言 → 签发 MCP 令牌 │ ▼ 后续用 MCP 令牌访问
这种机制让 MCP 复用已有身份系统,而非自己重新认证。
SDK 的身份断言扩展,让客户端把外部令牌带进来:
from mcp.client.auth.extensions import IdentityAssertionProvider # 已有的外部令牌(如 SSO 令牌) external_token = get_sso_token() # 从你的 SSO 系统拿 provider = IdentityAssertionProvider( external_token=external_token, # 外部身份令牌 token_url="https://mcp.example.com/token", # 其他配置 ) async with Client( "http://mcp.example.com/mcp", auth=provider, ) as client: # SDK 把外部令牌断言给 MCP,换 MCP 令牌 result = await client.call_tool(...)
身份断言流的关键:
服务端要支持身份断言,需要配置「信任哪些外部 IdP」:
# 服务端配置信任外部 IdP(概念性) mcp = MCPServer( "Protected", auth=AuthSettings( provider=my_provider, trusted_issuers=["sso.company.com"], # 信任的 IdP # 其他配置 ) )
配置后,服务端会:
⚠️ 注意:身份断言的核心是「信任」。服务端必须严格验证断言——签名是否来自可信 IdP、是否过期、是否被篡改。接受未经验证的断言=允许任何人伪造身份。实现身份断言时,务必遵循 OIDC/OAuth 的安全最佳实践。
把三种认证方式放一起,帮你选对:
| 方式 | 谁参与 | 适合 | 例子 |
|---|---|---|---|
| 授权码流(11.3) | 用户 + 客户端 | 有用户的交互应用 | 桌面应用、Web 应用 |
| 客户端凭证(本节) | 仅客户端(服务身份) | 机器到机器 | 服务间调用、定时任务 |
| 身份断言(本节) | 客户端 + 外部 IdP | 已有 SSO 的企业 | 公司内部 MCP 复用 SSO |
判断口诀:
问:有用户在浏览器里操作吗? 有 → 授权码流 无 → 继续问 问:调用方是服务(机器),还是有外部身份要复用? 服务 → 客户端凭证 外部身份 → 身份断言
第 11 章结束。你已经掌握 OAuth 全链路:角色、服务端授权服务器、客户端 OAuth、客户端凭证、身份断言。第 12 章讲进阶——低层 API、扩展、中间件、可观测性。