ARTICLE DETAIL

资讯详情

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

Spring AI 接入 Model Context Protocol:MCP Security 下的 OAuth 2.0 配置骨架与验证

Spring AI 接入 Model Context Protocol:MCP Security 下的 OAuth 2.0 配置骨架与验证 1. 为什么 Spring AI 接 MCP 时鉴权总卡住如果你正在用 Spring AI 把大模型接到外部工具上多半已经踩过 Model Context ProtocolMCP这道坎。MCP 解决的是一件很实在的事让模型通过统一协议去调用你本地的文件系统、数据库查询、内部 HTTP 接口而不是把工具逻辑硬编码进 prompt。Spring AI 提供了 MCP 客户端和服务端的 starter写几行配置就能让模型“看见”工具列表。但真正上生产时问题往往不在协议本身而在 MCP Security 这一层。MCP 规范里的授权部分还在制定中社区项目spring-ai-community/mcp-security用 OAuth 2.0 和 API Key 先把这块补上了。它给 MCP 服务端提供资源服务器能力给 MCP 客户端提供 OAuth 2.0 客户端支持还增强了 Spring Authorization Server 来做 MCP 专用的授权服务器。这篇面向的是需要给 MCP 服务端配置 OAuth 2.0 鉴权的 Java 开发者。我会交付三样东西可复制的 Spring AI MCP 客户端配置片段、OAuth 2.0 授权参数骨架、以及用最小请求验证整条鉴权链路是否生效的具体动作。适合已经能跑通 MCP 基础通信、但一加鉴权就 401 或启动报错的人。下面所有配置都基于 Streamable HTTP 传输因为 SSE 在服务端模块里已经不被支持。2. TaoToken 前置先把模型侧和工具侧的 Key 理清在配 MCP 鉴权之前建议先把模型调用这一侧的凭证理顺否则排查问题时容易把“模型 Key 失效”和“MCP 工具鉴权失败”混在一起。我习惯用 TaoToken 来统一管理模型访问它的 API 地址是https://taotoken.net/api控制台里可以创建和管理 API Keys。具体动作登录官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite进入控制台后创建一把 API Key。这把 Key 是给 Spring AI 的 ChatClient 调模型用的和后面 MCP 服务端的 OAuth 2.0 是两套独立凭证别搞混。创建完成后你可以先去模型对话页面https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite发一条消息确认 Key 本身可用。这一步很快但能省掉后面大量“到底是哪层挂了”的猜测。如果你打算长期跑编码类 Agent或者让 MCP 工具链持续工作可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它更适合高频调用的场景。Key 的管理入口在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入细节可以对照文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite。注意模型侧的 API Key 和 MCP 服务端的 OAuth 2.0 issuer 是两回事。前者管“能不能调模型”后者管“能不能调工具”。排查时先分层别一上来就怀疑 OAuth 配置。3. 可复制配置MCP 服务端 OAuth 2.0 骨架先看服务端。MCP 服务端安全模块只兼容基于 Spring WebMVC 的服务器WebFlux 不支持这点在选型时就要定下来。依赖需要三个mcp-server-security、spring-boot-starter-security以及可选的spring-boot-starter-oauth2-resource-server。dependencies dependency groupIdorg.springaicommunity/groupId artifactIdmcp-server-security/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-oauth2-resource-server/artifactId /dependency /dependenciesapplication.properties里先启用 MCP 服务端协议选STREAMABLEspring.ai.mcp.server.namemy-cool-mcp-server spring.ai.mcp.server.protocolSTREAMABLE spring.security.oauth2.resourceserver.jwt.issuer-urihttps://your-auth-server.example.com然后是安全过滤链。核心是用McpServerOAuth2Configurer把 MCP 服务端配成 OAuth 2.0 资源服务器authorizationServer传 issuer URIvalidateAudienceClaim控制是否校验 JWT 里的aud声明。Configuration EnableWebSecurity class McpServerConfiguration { Value(${spring.security.oauth2.resourceserver.jwt.issuer-uri}) private String issuerUrl; Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(auth - auth.anyRequest().authenticated()) .with( McpServerOAuth2Configurer.mcpServerOAuth2(), (mcpAuthorization) - { mcpAuthorization.authorizationServer(issuerUrl); mcpAuthorization.validateAudienceClaim(true); } ) .build(); } }如果你只想保护工具调用让initialize和tools/list保持公开可以改成放行/mcp再用PreAuthorize控制具体工具Configuration EnableWebSecurity EnableMethodSecurity class McpServerConfiguration { Value(${spring.security.oauth2.resourceserver.jwt.issuer-uri}) private String issuerUrl; Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(auth - { auth.requestMatcher(/mcp).permitAll(); auth.anyRequest().authenticated(); }) .with( McpServerOAuth2Configurer.mcpServerOAuth2(), (mcpAuthorization) - mcpAuthorization.authorizationServer(issuerUrl) ) .build(); } }工具方法上加注解即可SecurityContextHolder里能拿到当前认证信息Service public class MyToolsService { PreAuthorize(isAuthenticated()) McpTool(name greeter, description A tool that greets the user by name) public String greet( ToolParam(description The language for the greeting) String language ) { var authentication SecurityContextHolder.getContext().getAuthentication(); var name authentication.getName(); return Hello, %s!.formatted(name); } }客户端侧application.properties里要激活 OAuth2 客户端支持并声明授权服务器的 registration 和 providerspring.ai.mcp.client.typeSYNC spring.security.oauth2.client.registration.authserver.client-id客户端ID spring.security.oauth2.client.registration.authserver.client-secret客户端密钥 spring.security.oauth2.client.registration.authserver.authorization-grant-typeauthorization_code spring.security.oauth2.client.registration.authserver.providerauthserver spring.security.oauth2.client.provider.authserver.issuer-uri授权服务器ISSUER URI再配一个McpSyncHttpClientRequestCustomizer把 OAuth2 的 token 注入到 MCP 请求里Configuration class McpConfiguration { Bean McpCustomizerMcpClient.SyncSpec syncClientCustomizer() { return (name, syncSpec) - syncSpec.transportContextProvider( new AuthenticationMcpTransportContextProvider() ); } Bean McpSyncHttpClientRequestCustomizer requestCustomizer( OAuth2AuthorizedClientManager clientManager ) { return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer( clientManager, authserver ); } }4. 验证请求用最小动作确认鉴权链路生效配置写完不代表链路通了。我一般用三步验证从授权服务器到 MCP 服务端逐层确认。第一步确认授权服务器能发 token。用client_credentials流程直接打 token 端点curl -X POST https://your-auth-server.example.com/oauth2/token \ -H Content-Type: application/x-www-form-urlencoded \ -d grant_typeclient_credentials \ -d client_iddefault-client \ -d client_secretdefault-secret返回里应该有access_token和token_type: Bearer。如果这一步就失败问题在授权服务器配置跟 MCP 无关。第二步拿这个 token 去调 MCP 服务端的工具接口。假设服务端跑在 8080工具调用走/mcpcurl -X POST http://localhost:8080/mcp \ -H Authorization: Bearer 上一步拿到的access_token \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, method: tools/call, params: { name: greeter, arguments: { language: english } }, id: 1 }如果返回里result带了问候语说明 OAuth 2.0 资源服务器校验通过SecurityContextHolder也拿到了认证信息。如果返回 401看响应头里的WWW-Authenticate它会告诉你 token 是过期、签名不对还是 audience 不匹配。第三步在 Spring AI 客户端里跑一次完整调用确认SyncMcpToolCallbackProvider把工具挂上了var chatResponse chatClient.prompt(用 greeter 工具打个招呼) .tools(new SyncMcpToolCallbackProvider(mcpClient)) .call() .content();实测下来最容易出问题的是validateAudienceClaim(true)和授权服务器没配资源指示符RFC 8707的组合token 里没有aud校验直接失败。要么关掉这个校验要么在授权服务器侧把 audience 配上。5. 本篇常见错排查启动就报 WebFlux 不兼容MCP 服务端安全模块只支持 Spring WebMVC客户端模块的 WebFlux 支持也有限。检查你的spring-boot-starter-webflux是不是被间接引入了换成spring-boot-starter-web。401 但 token 明明有效先看 issuer URI 是否和 token 里的iss完全一致包括结尾斜杠。再看aud校验validateAudienceClaim(true)时 token 必须带正确的 audience。客户端启动时工具发现失败Spring AI 自动配置会在启动时初始化 MCP 客户端此时没有用户上下文授权码流程拿不到 token。解决办法是禁用Tool自动配置发布一个空的ToolCallbackResolverBean ToolCallbackResolver resolver() { return new StaticToolCallbackResolver(List.of()); }或者干脆编程式配置 MCP 客户端手动控制初始化时机。API Key 方式报 bcrypt 慢InMemoryApiKeyEntityRepository用 bcrypt 存 Key计算开销大只适合测试。生产环境自己实现ApiKeyEntityRepository别用它扛流量。不透明令牌不生效服务端模块只支持 JWT不透明令牌opaque token不支持。确认授权服务器发的是 JWT。MCP Inspector 连不上本地调试时可能需要临时禁用 CSRF 和 CORS 保护但别把这个配置带到生产。6. 接下来怎么走鉴权链路跑通后下一步通常是把它接到真实的编码或 Agent 工作流里。如果你要让 MCP 工具在 Claude Code 这类环境里长期工作可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它在高频工具调用场景下更省心。Claude Code 的接入细节在https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite。接入过程中如果遇到 token 注入或 issuer 配置的问题先回到 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite确认凭证状态再对照接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite逐项核对。MCP Security 这块规范还在演进配置骨架先跑通后面跟着社区版本升级就行。
返回列表