ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Spring AI的MCP Server/Client实现及鉴权:把鉴权配置改到TaoToken

基于Spring AI的MCP Server/Client实现及鉴权:把鉴权配置改到TaoToken 1. 从一次 401 说起Spring AI MCP 鉴权链路到底卡在哪如果你正在用 Spring AI 搭 MCP Server 和 MCP Client大概率会遇到这样一个场景本地把 Server 起在 8080Client 用 SSE 连过去工具方法也注册好了结果一发起对话日志里直接甩出一行401 Unauthorized或者更隐蔽一点Client 端报local proxy failed、reading choices之类的错看起来像是模型的问题实际上是鉴权头没带对。MCP 即模型上下文协议简单说就是让大模型通过统一协议去调用你本地的接口、数据库、文件服务。Spring AI 从 1.0 开始把 MCP Server 和 Client 的 starter 做得比较完整Java 后端接入门槛低了很多。但真正落到生产绕不开一个问题模型访问凭证和 MCP 服务凭证怎么统一管理。很多团队的做法是每个服务各存一份 KeyClient 里写死一个、Server 里再写死一个改一次要动好几个仓库。这篇就聚焦 Spring AI 框架下 MCP Server 与 Client 的鉴权链路面向需要统一管理模型访问凭证的 Java 后端场景。我会给出可复制的鉴权配置片段把鉴权配置改到 TaoToken 统一 Key 接入并完整演示一次从 401 报错到鉴权通过的验证动作。适合已经能跑通基础 MCP 调用、但被鉴权和凭证管理卡住的同学。核心检索词先摆出来Spring AI MCP Server Client 鉴权配置、MCP 统一 Key 接入、Spring AI MCP 401 排查。这三个词基本覆盖了本文要解决的问题域。先说清楚 MCP 的鉴权链路分两段。第一段是 Client 到 Server 之间走 HTTP 头通常是Authorization或者自定义的appCode/appSecretKey第二段是 Client 到模型服务之间走模型厂商的 API Key。传统做法这两段各管各的问题就出在这里模型 Key 散落在各个 Client 的 yml 里MCP Server 的鉴权又自成一套运维和轮换都很痛苦。我试过把这两段收敛到同一个入口也就是让 MCP Client 在调用模型和调用 MCP Server 时都从统一的凭证源取 Key。TaoToken 在这里扮演的就是统一凭证入口的角色它提供兼容 OpenAI 风格的 API 地址模型调用和 MCP 工具调用可以共用一套 Key 管理逻辑。下面从环境准备开始一步步把配置改过去。2. TaoToken 前置准备统一 Key 与 MCP 鉴权的关系在动手改配置之前先把 TaoToken 这一侧的准备做掉。这一步的目标很简单拿到一个可以同时用于模型调用和 MCP 鉴权的 Key并确认 API 地址可用。TaoToken 的 API 地址是https://taotoken.net/api这个地址不加任何 UTM 参数直接作为 Base URL 使用。模型对话入口在https://taotoken.net/modelsCoding Plan 在https://taotoken.net/coding-plan控制台在https://taotoken.net/consoleAPI Keys 管理在https://taotoken.net/api-keys接入文档在https://taotoken.net/doc。这些 deep link 在后续 CTA 里会带上归因参数正文里先记清楚路径。为什么要把 MCP 鉴权配置改到 TaoToken因为 MCP 的鉴权本质上是「谁有权调用这个工具」而模型调用的鉴权是「谁有权用这个模型」。在 Spring AI 的架构里MCP Client 同时持有这两个身份。如果两套凭证分开管理就会出现 Client 里配了模型 Key但 MCP Server 的过滤器校验的是另一套 token两边对不上就 401。统一到 TaoToken 之后逻辑变成Client 从 TaoToken 拿一个 Key这个 Key 既用于向模型服务发起请求也用于在请求 MCP Server 时放进Authorization头。MCP Server 侧的过滤器只需要校验这个 Key 的有效性不需要再维护一套独立的 appCode/appSecretKey。这样凭证轮换只在一个地方做审计也集中。具体操作上先去https://taotoken.net/api-keys创建一个 Key记下来。然后在项目里把它放到环境变量不要硬编码进 yml。Spring AI 的配置支持${}占位符这一点后面配置片段里会体现。这里有个容易踩的坑TaoToken 的 Key 在模型调用时通常放在Authorization: Bearer key而 MCP Server 的过滤器如果也读Authorization就要保证格式一致。如果你的 MCP Server 过滤器读的是自定义头比如X-MCP-Token那 Client 侧就要同时带两个头或者把过滤器改成读Authorization。本文统一用Authorization减少头数量。另外提醒一句MCP Server 建议独立成微服务不要和业务代码混在一起。混在一起的话业务侧的登录鉴权白名单要放行/sse、/mcp/**、/health、/actuator/health这些路径否则 MCP 请求会被业务拦截器先拦掉报的错和鉴权失败很像排查起来费时间。准备好 Key 和 API 地址后进入配置环节。下面给的片段都是可以直接复制到项目里的路径和字段名与 Spring AI 官方 starter 保持一致。3. 可复制配置把鉴权改到 TaoToken 的完整片段这一节是全文的技术核心给出 MCP Server 和 MCP Client 两侧的可复制配置。先看依赖再看 yml最后看鉴权过滤器和 Client 的请求头注入。MCP Server 侧依赖基于 Spring Boot 3.5.5 和 Spring AI 1.1.0-M3dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependencyMCP Server 的 yml 配置关键是sse-message-point和namespring: ai: mcp: server: name: mcp-server sse-message-point: /sseMCP Server 的鉴权过滤器改成校验 TaoToken 的 Key。这里用WebMvcConfigurer注册拦截器或者直接用Filter。下面给一个Filter版本读Authorization头Component Slf4j public class McpAuthFilter implements Filter { Value(${taotoken.api.key}) private String expectedKey; Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; String path req.getRequestURI(); if (path.startsWith(/sse) || path.startsWith(/mcp)) { String auth req.getHeader(Authorization); if (auth null || !auth.equals(Bearer expectedKey)) { HttpServletResponse resp (HttpServletResponse) response; resp.setStatus(401); resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write({\code\:401,\msg\:\认证失败: 无效令牌\}); return; } } chain.doFilter(request, response); } }MCP Client 侧依赖注意用 webflux因为 MCP Client 的 SSE 是响应式的dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependencyMCP Client 的 yml把模型 Base URL 指向 TaoToken同时配置 MCP Server 连接spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini mcp: client: enabled: true toolcallback: enabled: true name: note-mcp-client sse: connections: server1: url: http://localhost:8080 sse-endpoint: /sse type: SYNC注意这里base-url用的是https://taotoken.net/api不带 UTM。api-key从环境变量TAOTOKEN_API_KEY读和 MCP Server 过滤器里的taotoken.api.key是同一个值。这样模型调用和 MCP 鉴权共用一套 Key。MCP Client 在发起 SSE 连接时需要把Authorization头带上。Spring AI 的 MCP Client 默认不会自动加这个头需要自定义WebClient或者用McpSseClientProperties扩展。下面给一个配置类注入带鉴权头的WebClientConfiguration public class McpClientConfig { Value(${taotoken.api.key}) private String apiKey; Bean public WebClient.Builder mcpWebClientBuilder() { return WebClient.builder() .defaultHeader(Authorization, Bearer apiKey); } }如果你的 Spring AI 版本里 MCP Client 的 SSE 连接不走这个WebClient.Builder那就退一步在 MCP Server 的过滤器里放宽为只校验 Key 是否存在且非空把严格校验放到模型调用侧。但推荐还是让 Client 带上头链路更清晰。ChatClient 的配置把 MCP 工具提供者注入进去Bean public ChatClient chatClient(OpenAiChatModel model, ToolCallbackProvider toolCallbackProvider) { return ChatClient.builder(model) .defaultSystem(你是笔记助手基于 MCP 工具返回的内容回答用户问题) .defaultToolCallbacks(toolCallbackProvider) .build(); }到这里配置片段就齐了。Server 侧校验AuthorizationClient 侧注入Authorization模型 Base URL 指向 TaoTokenKey 统一从环境变量取。下一步验证这套配置能不能跑通。4. 验证请求从 401 到鉴权通过的完整动作配置改完先别急着跑完整对话按顺序验证能快速定位问题在哪一段。第一步单独验证 MCP Server 的鉴权。用 curl 直接打 SSE 端点不带 Authorizationcurl -i http://localhost:8080/sse预期返回 401body 是{code:401,msg:认证失败: 无效令牌}。这一步确认过滤器生效了。第二步带上正确的 Authorization 再打一次curl -i -H Authorization: Bearer $TAOTOKEN_API_KEY http://localhost:8080/sse预期返回 200并且开始输出 SSE 事件流。如果这一步还是 401检查环境变量是否真的注入到了 Server 进程以及过滤器里expectedKey的值和请求头里的 Key 是否完全一致注意 Bearer 后面有一个空格。第三步验证模型调用。单独用 curl 打 TaoToken 的 chat completionscurl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}]}预期返回一个正常的 JSON包含choices字段。如果这里报 401说明 Key 本身有问题去https://taotoken.net/api-keys确认 Key 状态。如果报reading choices之类的解析错通常是返回体不是预期格式检查 Base URL 是不是写成了https://taotoken.net/api而不是带/v1的路径Spring AI 的 OpenAI starter 会自动拼/v1/chat/completions。第四步跑完整的 MCP 对话。启动 Client调用/ai/note/chatForNote接口curl -N -H Authorization: Bearer $TAOTOKEN_API_KEY \ http://localhost:8081/ai/note/chatForNote?message我的钥匙在哪儿预期看到 SSE 流式输出模型先决定调用 MCP 工具工具返回笔记内容模型再基于内容回答。日志里应该能看到 MCP Server 侧打印出工具方法的入参以及 Client 侧打印出模型返回。如果第四步失败回看第三步和第二步是否都通过。两步都通过但第四步失败问题通常在 Client 到 Server 的 SSE 连接头没带上或者 MCP Server 的/mcp/**路径被业务拦截器拦了。检查 Client 的WebClient是否真的注入了Authorization以及 Server 侧白名单是否放行了/mcp/**。验证通过后你会看到一次完整的链路Client 带 Key 连 ServerServer 校验 KeyClient 带同一个 Key 调模型模型返回工具调用指令Client 转发给 ServerServer 执行工具返回结果模型润色后流式返回。整条链路只有一个 Key轮换时只改环境变量。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节把实际会遇到的报错列出来对照排查。每个报错都给出触发条件和处理方式。401 Unauthorized 是最常见的。触发条件有三种Client 没带Authorization头、头里的 Key 和 Server 期望的不一致、Key 本身失效。排查顺序是先 curl Server 端点确认过滤器行为再 curl 模型端点确认 Key 有效最后检查 Client 的WebClient是否真的注入了头。注意 Spring AI 不同版本里 MCP Client 的 SSE 连接构造方式不同有的版本需要显式传WebClient有的走默认。如果注入不生效可以在 Client 侧加一个日志打印实际发出的请求头。local proxy failed 通常出现在 Client 侧原因是 Client 到 Server 的 SSE 连接建立失败。可能是 Server 没起、端口不对、sse-endpoint配错或者 Server 侧过滤器返回了非 SSE 格式的响应导致 Client 解析失败。先确认curl -i http://localhost:8080/sse带 Key 能返回 200 和事件流再检查 Client 的url和sse-endpoint拼接是否正确。注意url不要带尾部斜杠sse-endpoint要以斜杠开头。reading choices 是模型返回体解析失败。Spring AI 的 OpenAI starter 期望返回体里有choices数组。如果 Base URL 配错比如配成了https://taotoken.net而不是https://taotoken.net/api请求会打到错误路径返回 HTML 或错误 JSON解析就失败。检查base-url是否精确为https://taotoken.net/api以及模型名是否在 TaoToken 支持的列表里。模型名写错有时也会返回非预期结构。OAuth 相关报错如果你用的是 Claude Code 或者带 OAuth 的 MCP 接入方式可能会遇到 token 过期或 scope 不足。这类报错的关键是确认 OAuth 流程拿到的 token 有没有正确传给 MCP Client。如果同时用了 TaoToken 的 Key 和 OAuth token要分清哪个头传哪个。一般Authorization只放一个不要叠加。Claude Code 接入场景下Base URL、Key、Model ID 三件套要写全缺一个都会报鉴权或模型不存在。还有一个隐蔽的错MCP Server 和业务代码混部时业务拦截器先返回 401但 body 格式和 MCP 过滤器的不一样Client 侧看到的报错信息会误导。排查时先看 Server 日志里是哪个过滤器打的日志确认是 MCP 过滤器还是业务过滤器。对照表如下报错常见原因处理401 Unauthorized头缺失/Key 不一致/Key 失效curl 分段验证检查环境变量注入local proxy failedSSE 连接建立失败确认 Server 可达、endpoint 拼接正确reading choicesBase URL 或模型名错误确认 base-url 为 https://taotoken.net/apiOAuth 报错token 未传递或 scope 不足确认三件套 Base URLKeyModel ID 写全排查的核心思路是分段验证先 Server 鉴权再模型调用最后完整链路。不要一上来就跑完整对话那样报错信息会混在一起。6. 统一 Key 之后的接入与长期使用建议把鉴权配置改到 TaoToken 之后最直接的变化是凭证管理收敛了。以前模型 Key 和 MCP 鉴权 Key 分开现在一个环境变量搞定。轮换时改一处所有 Client 和 Server 重启后生效。审计时也清楚哪个 Key 调了哪些模型、哪些工具都在一个入口。如果你还在接入阶段建议先把 MCP Server 的鉴权跑通再配 Client 的模型调用最后合起来验证。接入文档在https://taotoken.net/doc里面有 Base URL 和请求格式的说明。API Keys 管理在https://taotoken.net/api-keys创建和吊销都在这里。模型列表和对话测试可以在https://taotoken.net/models直接试。对于长期跑编码 Agent 或者多 MCP Server 的场景Coding Plan 在https://taotoken.net/coding-plan适合需要稳定额度和统一计费的团队。控制台在https://taotoken.net/console可以看调用量和 Key 使用情况。最后给一个实用技巧在 Client 侧加一个请求日志拦截器把发往 MCP Server 和模型服务的请求头打出来但记得脱敏 Key。这样下次再遇到 401直接看日志就知道头带没带、值对不对比翻代码快得多。MCP 鉴权链路本身不复杂复杂的是凭证散落导致的排查成本统一到 TaoToken 之后这条链路就清晰了。
返回列表