多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Spring AI 干货笔记之 MCP 安全:把 MCP 服务端鉴权配置改到 TaoToken

Spring AI 干货笔记之 MCP 安全:把 MCP 服务端鉴权配置改到 TaoToken 1. 从一次 401 说起Spring AI MCP 客户端为什么总在鉴权上翻车如果你已经用 Spring AI 把 MCP 客户端在本地跑通了大概率经历过这个场景initialize和tools/list都正常日志里能看到工具列表被拉回来但真正发起一次工具调用时服务端直接甩回一个 401或者客户端日志里出现local proxy failed、reading choices之类的报错。问题往往不在 MCP 协议本身而在鉴权信息散落在各处——application.yml里一份、代码里硬编码一份、环境变量里又一份改了一处忘了另一处。MCPModel Context Protocol是让大模型通过标准化接口调用外部工具和资源的协议Spring AI 从 1.1.x 分支开始提供了社区驱动的mcp-security模块覆盖服务端 OAuth 2.0 资源服务器、客户端 OAuth 2.0 授权、以及带 MCP 特定功能的授权服务器三块能力。它适合谁适合那些已经能跑通 MCP 客户端、但需要把鉴权与密钥管理统一收口的开发者。本文要做的就是把 MCP 服务端的 endpoint 与鉴权配置整体改到 TaoToken用一次真实的工具调用验证 401 是否消失、调用链是否正常返回。先说清楚一个前提TaoToken 在这里扮演的是统一入口与密钥管理的角色你通过它拿到 Base URL 和 API Key再把 MCP 客户端指向这个地址。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。下面所有配置都围绕这两个地址展开。我试过把鉴权信息从代码里抽出来集中管理最直接的收益是排障时只需要看一个地方。接下来按「原问题 → 前置准备 → 可复制配置 → 验证请求 → 错排查 → 收口」的顺序走一遍每一步都给完整片段。2. TaoToken 前置拿到 Base URL 与 API Key 并理解它在 MCP 链路里的位置在动手改配置之前先把 TaoToken 这一侧的准备做扎实。你需要两样东西Base URL 和 API Key。Base URL 就是 https://taotoken.net/api API Key 在控制台的 API Keys 页面创建。创建入口是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 进去之后新建一个 Key复制出来保存好后面application.yml里要用。这里要理解 TaoToken 在 MCP 链路里的位置。MCP 客户端发起工具调用时请求会先到 MCP 服务端的 endpoint服务端再根据鉴权配置决定是否放行。把 endpoint 与鉴权信息改到 TaoToken本质上是让 MCP 客户端在请求头里带上 TaoToken 签发的凭证由统一入口完成校验而不是在每个服务端各写一套密钥。这样做的好处是密钥只有一份轮换时只改一个地方。如果你还没创建 Key现在去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 建一个。建完之后建议先用模型对话页面做一次连通性确认地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 随便发一句话能正常返回就说明 Key 和 Base URL 没问题。这一步能帮你排除掉「Key 本身无效」这类低级问题省得后面在 MCP 配置里绕圈。关于 Model IDMCP 客户端在调用工具时通常还需要指定底层模型。你可以在模型对话页面确认当前可用的 Model ID常见的有claude-sonnet-4-5、gpt-4o这类。记住这个 ID后面配置里要填。如果你打算长期跑编码类 Agent可以顺带看一下 Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合高频调用场景。前置准备清单Base URL https://taotoken.net/api API Key 控制台创建Model ID 模型对话页确认。三样齐了再往下走。3. 可复制配置application.yml 与 MCP 客户端鉴权片段这一节是全文的核心所有片段都可以直接复制。先给application.yml再给 MCP 客户端侧的鉴权配置。注意路径和原文保持一致不要自己改字段名。先看application.yml。这里把 MCP 服务端的 endpoint 指向 TaoToken同时把鉴权信息集中到spring.security.oauth2下面spring: application: name: mcp-client-demo ai: mcp: client: type: SYNC name: taotoken-mcp-client version: 1.0.0 request-timeout: 30s streamable-http: connections: taotoken-server: url: https://taotoken.net/api endpoint: /mcp security: oauth2: client: registration: taotoken: client-id: ${TAOTOKEN_CLIENT_ID} client-secret: ${TAOTOKEN_API_KEY} authorization-grant-type: client_credentials provider: taotoken provider: taotoken: issuer-uri: https://taotoken.net/api这段配置里几个关键点。spring.ai.mcp.client.type必须是SYNC因为mcp-client-security模块目前只支持McpSyncClient用异步会直接报错。streamable-http.connections下面的url填 TaoToken 的 Base URLendpoint填/mcp两者拼起来就是完整的 MCP 服务端地址。client-id和client-secret用环境变量注入不要把 Key 明文写进文件这是密钥管理的基本纪律。再看 MCP 客户端侧的鉴权配置类。如果你用的是基于 HttpClient 的客户端来自spring-ai-starter-mcp-client需要注册一个McpSyncHttpClientRequestCustomizerConfiguration class McpClientSecurityConfig { Bean McpSyncClientCustomizer syncClientCustomizer() { return (name, syncSpec) - syncSpec.transportContextProvider( new AuthenticationMcpTransportContextProvider() ); } Bean McpSyncHttpClientRequestCustomizer requestCustomizer( OAuth2AuthorizedClientManager clientManager) { return new OAuth2ClientCredentialsSyncHttpRequestCustomizer( clientManager, taotoken ); } }这里的taotoken必须和application.yml里registration下的名称完全一致大小写敏感。OAuth2ClientCredentialsSyncHttpRequestCustomizer对应客户端凭证流程适合机器对机器的场景不需要人工干预。如果你用的是基于 WebClient 的客户端来自spring-ai-starter-mcp-client-webflux把 customizer 换成McpOAuth2ClientCredentialsExchangeFilterFunction注入到WebClient.Builder里即可。还有一个容易忽略的点Spring AI 的自动配置会在应用启动时初始化 MCP 客户端这可能触发基于用户身份的鉴权问题。如果你遇到启动阶段就报鉴权失败可以发布一个空的ToolCallbackResolverbean 来禁用Tool自动配置Bean ToolCallbackResolver resolver() { return new StaticToolCallbackResolver(List.of()); }三件套在这里齐了Base URL https://taotoken.net/api Key 环境变量TAOTOKEN_API_KEYModel ID 你在模型对话页确认的那个。配置写完后把TAOTOKEN_CLIENT_ID和TAOTOKEN_API_KEY两个环境变量设好再启动。4. 验证请求用一次工具调用确认 401 消失、调用链正常返回配置写完不算完得用一次真实的工具调用来验证。先定义一个最简单的 MCP 工具比如一个打招呼的工具Service public class GreeterTools { McpTool(name greeter, description A tool that greets you in the selected language) PreAuthorize(isAuthenticated()) public String greet( ToolParam(description The language for the greeting) String language) { if (language null || language.isBlank()) { language english; } return switch (language.toLowerCase()) { case english - Hello you!; case french - Salut toi!; default - Hello!; }; } }注意PreAuthorize(isAuthenticated())这行它要求调用方必须通过鉴权。如果鉴权配置没生效这次调用会直接返回 401正好用来验证。启动应用后用 curl 发一次工具调用请求。先拿 token再调工具# 第一步用 client_credentials 拿 access token curl -X POST https://taotoken.net/api/oauth/token \ -H Content-Type: application/x-www-form-urlencoded \ -d grant_typeclient_credentials \ -d client_id${TAOTOKEN_CLIENT_ID} \ -d client_secret${TAOTOKEN_API_KEY} # 第二步带上 token 调用 MCP 工具 curl -X POST https://taotoken.net/api/mcp \ -H Authorization: Bearer ${ACCESS_TOKEN} \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 1, method: tools/call, params: { name: greeter, arguments: { language: french } } }预期结果是第二步返回result: { content: [{ type: text, text: Salut toi! }] }。如果第一步拿不到 token说明 client_id 或 client_secret 有问题如果第一步成功但第二步 401说明 token 没被正确带上检查Authorization头格式。实测下来只要application.yml里的issuer-uri和registration名称对得上401 会直接消失。调用链正常返回的标志是token 获取成功 → 工具调用返回文本内容 → 日志里没有local proxy failed或reading choices报错。如果这三条都满足说明 endpoint 与鉴权信息已经成功改到 TaoToken。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 报错对照这一节把真实会遇到的报错列出来对照着改。401 Unauthorized。最常见的原因是client-id或client-secret填错或者环境变量没生效。检查application.yml里用的是${TAOTOKEN_CLIENT_ID}这种占位符启动时环境变量必须存在。另一个原因是registration名称和 customizer 里的名称不一致比如 yml 里写taotoken代码里写taotoken-client这种大小写或拼写差异会直接导致鉴权失败。local proxy failed。这个报错通常出现在客户端尝试连接 MCP 服务端时endpoint 拼错了。检查url和endpoint拼接后的完整地址是不是https://taotoken.net/api/mcp。如果url末尾多了斜杠或者endpoint少了斜杠拼出来就是错的。另外确认spring.ai.mcp.client.type是SYNC异步客户端在这个模块下不支持。reading choices。这个报错一般出现在模型返回阶段说明请求已经过了鉴权但底层模型调用出了问题。检查 Model ID 是否填对以及request-timeout是否太短。如果工具调用本身耗时较长把request-timeout从30s调到60s试试。OAuth 相关报错。如果日志里出现invalid_client或unauthorized_client说明authorization-grant-type和实际使用的 customizer 不匹配。client_credentials流程必须配OAuth2ClientCredentialsSyncHttpRequestCustomizer如果你写成了OAuth2AuthorizationCodeSyncHttpRequestCustomizer就会报这个错。反过来授权码流程配了客户端凭证 customizer 也一样。启动阶段鉴权失败。前面提过Spring AI 自动配置会在启动时初始化 MCP 客户端如果此时没有用户上下文基于用户身份的鉴权会失败。解决办法是发布空的ToolCallbackResolverbean或者改用编程式客户端配置手动构建McpSyncClient并注入AuthenticationMcpTransportContextProvider。排查顺序建议先确认环境变量 → 再确认 yml 字段 → 再确认 customizer 类型 → 最后看 endpoint 拼接。按这个顺序走大部分问题能在五分钟内定位。6. 收口把鉴权配置集中到 TaoToken 后的日常维护配置改到 TaoToken 之后日常维护会简单很多。密钥只有一份轮换时改环境变量重启即可不用去每个服务端改一遍。MCP 服务端的 endpoint 也统一了新增工具时只需要在McpTool注解上写方法鉴权由PreAuthorize统一控制。如果你后续要接入 Claude Code 或 Cline 这类工具Base URL 填 https://taotoken.net/api Key 填控制台创建的 API KeyModel ID 填模型对话页确认的那个三件套保持一致即可。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有各客户端的详细配置示例。最后给一个实用技巧把TAOTOKEN_CLIENT_ID和TAOTOKEN_API_KEY写进.env文件用spring.config.importoptional:file:.env[.properties]加载这样本地开发不用每次手动 export。生产环境用密钥管理服务注入不要提交到代码仓库。密钥管理这件事集中到一处永远比散落各处好维护。
返回列表