Spring AI 1.x 系列【46】MCP Security 模块

1. 概述

注意: 此模块仍处于开发阶段,文档和 API 可能在后续版本中发生变更。

Spring AI MCP Security 模块为 Spring AI 的模型上下文协议(MCP)实现提供了全面的 OAuth 2.0API Key 安全支持。该社区驱动的项目使开发者能够使用行业标准的认证与授权机制来保护 MCP 服务端和客户端。

此模块属于 spring-ai-community/mcp-security 项目,目前仅兼容 Spring AI 1.1.x 分支。它是社区驱动项目,尚未获得 Spring AIMCP 项目的官方背书。

三大核心组件:

组件 用途
MCP Server Security Spring AI MCP 服务端提供 OAuth 2.0 资源服务器和 API Key 认证
MCP Client Security Spring AI MCP 客户端提供 OAuth 2.0 客户端支持
MCP Authorization Server 基于 Spring Authorization Server 增强,针对 MCP 场景定制

核心能力:

  • 使用 OAuth 2.0API Key 保护 MCP 服务端
  • MCP 客户端配置 OAuth 2.0 授权流程
  • 搭建专为 MCP 工作流设计的授权服务器
  • 实现对 MCP 工具和资源的细粒度访问控制

2. MCP 服务端安全

Spring AI MCP 服务端提供 OAuth 2.0 资源服务器能力,同时提供基础的 API Key 认证支持。

注意: 此模块仅兼容 Spring WebMVC 服务端。

2.1 依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springaicommunity</groupId>
        <artifactId>mcp-server-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- 可选:OAuth2 支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
</dependencies>

2.2 OAuth 2.0 配置

基础 OAuth 2.0 配置

首先在 application.properties 中启用 MCP 服务端:

properties 复制代码
spring.ai.mcp.server.name=my-cool-mcp-server
# 支持的协议:STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLE

然后使用 Spring Security 标准 API 结合 MCP 配置器进行安全配置:

java 复制代码
@Configuration
@EnableWebSecurity
class McpServerConfiguration {

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuerUrl;

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // 对 EVERY 请求强制要求 token 认证
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                // 在 MCP 服务端上配置 OAuth2
                .with(
                        McpServerOAuth2Configurer.mcpServerOAuth2(),
                        (mcpAuthorization) -> {
                            // 必填:issuerURI
                            mcpAuthorization.authorizationServer(issuerUrl);
                            // 可选:强制校验 JWT 中的 `aud` 声明
                            // 并非所有授权服务器都支持 resource indicators,因此可能缺失
                            // 默认值为 false
                            // 参见 RFC 8707 Resource Indicators for OAuth 2.0
                            mcpAuthorization.validateAudienceClaim(true);
                        }
                )
                .build();
    }
}

仅保护工具调用

可以配置服务端仅对工具调用进行安全保护,其他 MCP 操作(如 initializetools/list)保持公开:

java 复制代码
@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();
                })
                // 在 MCP 服务端上配置 OAuth2
                .with(
                        McpResourceServerConfigurer.mcpServerOAuth2(),
                        (mcpAuthorization) -> {
                            // 必填:issuerURI
                            mcpAuthorization.authorizationServer(issuerUrl);
                        }
                )
                .build();
    }
}

然后使用 @PreAuthorize 注解结合方法安全来保护工具调用:

java 复制代码
@Service
public class MyToolsService {

    @PreAuthorize("isAuthenticated()")
    @McpTool(name = "greeter", description = "用你选择的语言向你问好")
    public String greet(
            @ToolParam(description = "问好的语言(例:english、french...)") String language
    ) {
        if (!StringUtils.hasText(language)) {
            language = "";
        }
        return switch (language.toLowerCase()) {
            case "english" -> "Hello you!";
            case "french" -> "Salut toi!";
            default -> "我不懂 \"%s\" 语言,所以只能说 Hello!".formatted(language);
        };
    }
}

也可以通过 SecurityContextHolder 在工具方法中直接访问当前认证信息:

java 复制代码
@McpTool(name = "greeter", description = "用指定语言向用户称呼名字问好")
@PreAuthorize("isAuthenticated()")
public String greet(
        @ToolParam(description = "问好的语言(例:english、french...)") String language
) {
    if (!StringUtils.hasText(language)) {
        language = "";
    }
    var authentication = SecurityContextHolder.getContext().getAuthentication();
    var name = authentication.getName();
    return switch (language.toLowerCase()) {
        case "english" -> "Hello, %s!".formatted(name);
        case "french" -> "Salut %s!".formatted(name);
        default -> ("我不懂 \"%s\" 语言,所以只能说 Hello %s!").formatted(language, name);
    };
}

2.3 API Key 认证

MCP Server Security 模块也支持基于 API Key 的认证。你需要提供自己的 ApiKeyEntityRepository 实现来存储 ApiKeyEntity 对象。

框架提供了 InMemoryApiKeyEntityRepository 示例实现和默认的 ApiKeyEntityImpl

注意: InMemoryApiKeyEntityRepository 使用 bcrypt 存储 API Key,计算开销较高,不适合高并发生产环境。生产环境请实现自己的 ApiKeyEntityRepository

java 复制代码
@Configuration
@EnableWebSecurity
class McpServerConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
                .with(
                        mcpServerApiKey(),
                        (apiKey) -> {
                            // 必填:API Key 的存储仓库
                            apiKey.apiKeyRepository(apiKeyRepository());

                            // 可选:携带 API Key 的请求头名称
                            // 例如此处,API Key 将通过 "CUSTOM-API-KEY: <value>" 发送
                            // 替代 .authenticationConverter(...)(见下方)
                            //
                            // apiKey.headerName("CUSTOM-API-KEY");

                            // 可选:自定义转换器,将 HTTP 请求转为认证对象
                            // 适用于 header 为 "Authorization: Bearer <value>" 的场景
                            // 替代 .headerName(...)(见上方)
                            //
                            // apiKey.authenticationConverter(request -> {
                            //     var key = extractKey(request);
                            //     return ApiKeyAuthenticationToken.unauthenticated(key);
                            // });
                        }
                )
                .build();
    }

    /**
     * 提供 {@link ApiKeyEntity} 的存储仓库
     */
    private ApiKeyEntityRepository<ApiKeyEntityImpl> apiKeyRepository() {
        var apiKey = ApiKeyEntityImpl.builder()
                .name("test api key")
                .id("api01")
                .secret("mycustomapikey")
                .build();

        return new InMemoryApiKeyEntityRepository<>(List.of(apiKey));
    }
}

配置完成后,可通过请求头 X-API-key: api01.mycustomapikey 调用 MCP 服务端。

2.4 已知限制

限制 说明
SSE 不支持 已弃用的 SSE 传输不受支持,请使用 Streamable HTTPStateless 传输
WebFlux 不支持 不支持基于 WebFlux 的服务端
Opaque Token 不支持 请使用 JWT

3. MCP 客户端安全

Spring AI MCP 客户端提供 OAuth 2.0 支持,同时兼容基于 HttpClient 的客户端(spring-ai-starter-mcp-client)和基于 WebClient 的客户端(spring-ai-starter-mcp-client-webflux)。

注意: 此模块仅支持 McpSyncClient

3.1 依赖

xml 复制代码
<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>mcp-client-security</artifactId>
</dependency>

3.2 授权流程

三种 OAuth 2.0 授权流程可供选择:

流程 适用场景
授权码流程authorization_code 用户级别权限,所有 MCP 请求均在用户请求上下文中发起
客户端凭证流程client_credentials 机器对机器通信,无人工参与
混合流程(Hybrid) 部分操作(如 initializetools/list)无需用户在场即可执行,但工具调用需要用户级别权限

选择建议:当使用 Spring Boot 属性配置 MCP 客户端时,推荐使用混合流程,因为工具发现是在启动时进行的,此时并无用户在场。

3.3 通用配置

所有流程都需要在 application.properties 中激活 Spring Security OAuth2 客户端支持:

yml 复制代码
# 确保 MCP 客户端为同步模式
spring.ai.mcp.client.type=SYNC

# 授权码流程或混合流程
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-type=authorization_code
spring.security.oauth2.client.registration.authserver.provider=authserver

# 客户端凭证流程或混合流程
spring.security.oauth2.client.registration.authserver-client-credentials.client-id=<客户端ID>
spring.security.oauth2.client.registration.authserver-client-credentials.client-secret=<客户端密钥>
spring.security.oauth2.client.registration.authserver-client-credentials.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.authserver-client-credentials.provider=authserver

# 授权服务器配置
spring.security.oauth2.client.provider.authserver.issuer-uri=<授权服务器的ISSUER URI>

然后创建配置类激活 OAuth2 客户端能力:

java 复制代码
@Configuration
@EnableWebSecurity
class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // 此示例中,客户端应用自身端点无安全保护
                .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
                // 开启 OAuth2 支持
                .oauth2Client(Customizer.withDefaults())
                .build();
    }
}

3.4 HttpClient 客户端

使用 spring-ai-starter-mcp-client 时,配置 McpSyncHttpClientRequestCustomizer Bean:

java 复制代码
@Configuration
class McpConfiguration {

    @Bean
    McpSyncClientCustomizer syncClientCustomizer() {
        return (name, syncSpec) ->
                syncSpec.transportContextProvider(
                        new AuthenticationMcpTransportContextProvider()
                );
    }

    @Bean
    McpSyncHttpClientRequestCustomizer requestCustomizer(
            OAuth2AuthorizedClientManager clientManager
    ) {
        // clientRegistration 名称 "authserver" 须与 application.properties 中一致
        return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(
                clientManager,
                "authserver"
        );
    }
}

可用的 Customizer 实现:

对应流程
OAuth2AuthorizationCodeSyncHttpRequestCustomizer 授权码流程
OAuth2ClientCredentialsSyncHttpRequestCustomizer 客户端凭证流程
OAuth2HybridSyncHttpRequestCustomizer 混合流程

3.5 WebClient 客户端

使用 spring-ai-starter-mcp-client-webflux 时,通过 MCP ExchangeFilterFunction 配置 WebClient.Builder

java 复制代码
@Configuration
class McpConfiguration {

    @Bean
    McpSyncClientCustomizer syncClientCustomizer() {
        return (name, syncSpec) ->
                syncSpec.transportContextProvider(
                        new AuthenticationMcpTransportContextProvider()
                );
    }

    @Bean
    WebClient.Builder mcpWebClientBuilder(OAuth2AuthorizedClientManager clientManager) {
        // clientRegistration 名称 "authserver" 须与 application.properties 中一致
        return WebClient.builder().filter(
                new McpOAuth2AuthorizationCodeExchangeFilterFunction(
                        clientManager,
                        "authserver"
                )
        );
    }
}

可用的 Filter 实现:

对应流程
McpOAuth2AuthorizationCodeExchangeFilterFunction 授权码流程
McpOAuth2ClientCredentialsExchangeFilterFunction 客户端凭证流程
McpOAuth2HybridExchangeFilterFunction 混合流程

3.6 绕过 Spring AI 自动配置

Spring AI 的自动配置会在启动时初始化 MCP 客户端,这可能导致基于用户认证的场景出问题。以下提供两种绕过方案:

方案一:禁用 @Tool 自动配置

发布一个空的 ToolCallbackResolver Bean 来禁用 Spring AI@Tool 自动配置:

java 复制代码
@Configuration
public class McpConfiguration {

    @Bean
    ToolCallbackResolver resolver() {
        return new StaticToolCallbackResolver(List.of());
    }
}

方案二:编程式客户端配置

以编程方式配置 MCP 客户端,替代 Spring Boot 属性配置。

HttpClient 客户端:

java 复制代码
@Bean
McpSyncClient client(
        ObjectMapper objectMapper,
        McpSyncHttpClientRequestCustomizer requestCustomizer,
        McpClientCommonProperties commonProps
) {
    var transport = HttpClientStreamableHttpTransport.builder(mcpServerUrl)
            .clientBuilder(HttpClient.newBuilder())
            .jsonMapper(new JacksonMcpJsonMapper(objectMapper))
            .httpRequestCustomizer(requestCustomizer)
            .build();

    var clientInfo = new McpSchema.Implementation("client-name", commonProps.getVersion());

    return McpClient.sync(transport)
            .clientInfo(clientInfo)
            .requestTimeout(commonProps.getRequestTimeout())
            .transportContextProvider(new AuthenticationMcpTransportContextProvider())
            .build();
}

WebClient 客户端:

java 复制代码
@Bean
McpSyncClient client(
        WebClient.Builder mcpWebClientBuilder,
        ObjectMapper objectMapper,
        McpClientCommonProperties commonProperties
) {
    var builder = mcpWebClientBuilder.baseUrl(mcpServerUrl);
    var transport = WebClientStreamableHttpTransport.builder(builder)
            .jsonMapper(new JacksonMcpJsonMapper(objectMapper))
            .build();

    var clientInfo = new McpSchema.Implementation("clientName", commonProperties.getVersion());

    return McpClient.sync(transport)
            .clientInfo(clientInfo)
            .requestTimeout(commonProperties.getRequestTimeout())
            .transportContextProvider(new AuthenticationMcpTransportContextProvider())
            .build();
}

然后将客户端添加到 ChatClient 中:

java 复制代码
var chatResponse = chatClient.prompt("提示 LLM 执行操作")
        .toolCallbacks(new SyncMcpToolCallbackProvider(mcpClient1, mcpClient2, mcpClient3))
        .call()
        .content();

3.7 已知限制

限制 说明
WebFlux 服务端不支持 不支持 Spring WebFlux 服务端
启动时自动初始化 Spring AI 自动配置在应用启动时初始化 MCP 客户端,基于用户认证的场景需要绕过方案
SSE 兼容 不同于服务端模块,客户端实现支持 SSE 传输(HttpClientWebClient 均可)

4. MCP 授权服务器

MCP Authorization Server 模块在 Spring Security OAuth 2.0 Authorization Server 基础上进行了增强,新增了动态客户端注册(Dynamic Client Registration)和资源指示符(Resource Indicators)等 MCP 授权规范相关特性。

4.1 依赖

xml 复制代码
<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>mcp-authorization-server</artifactId>
</dependency>

4.2 配置

application.yml 中配置授权服务器:

yaml 复制代码
spring:
  application:
    name: sample-authorization-server
  security:
    oauth2:
      authorizationserver:
        client:
          default-client:
            token:
              access-token-time-to-live: 1h
            registration:
              client-id: "default-client"
              client-secret: "{noop}default-secret"
              client-authentication-methods:
                - "client_secret_basic"
                - "none"
              authorization-grant-types:
                - "authorization_code"
                - "client_credentials"
              redirect-uris:
                - "http://127.0.0.1:8080/authorize/oauth2/code/authserver"
                - "http://localhost:8080/authorize/oauth2/code/authserver"
                # mcp-inspector
                - "http://localhost:6274/oauth/callback"
                # Claude Code
                - "https://claude.ai/api/mcp/auth_callback"
            user:
              # 单用户,用户名为 "user"
              name: user
              password: password

server:
  servlet:
    session:
      cookie:
        # 覆盖默认 cookie 名称(JSESSIONID)
        # 允许多个 Spring 应用在 localhost 上运行且各自拥有独立的 cookie
        # 否则,由于 cookie 不考虑端口号,会产生冲突
        name: MCP_AUTHORIZATION_SERVER_SESSIONID

然后通过安全过滤器链激活授权服务器能力:

java 复制代码
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            // 所有请求均需认证
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            // 启用授权服务器自定义
            .with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())
            // 启用表单登录,用户 "user" / "password"
            .formLogin(withDefaults())
            .build();
}

4.3 已知限制

限制 说明
WebFlux 不支持 不支持 Spring WebFlux 服务端
资源标识符 所有客户端支持所有资源标识符(resource identifiers)

5. 示例与集成

samples 目录包含所有模块的工作示例,包括集成测试。

通过 mcp-server-security 和配套的 mcp-authorization-server,可与以下工具集成:

  • Cursor
  • Claude Desktop
  • MCP Inspector

使用 MCP Inspector 时,可能需要禁用 CSRFCORS 保护。


6. 参考资源

相关推荐
CRMEB系统商城1 小时前
CRMEB多商户系统(Java)v2.3公测版发布
java·开发语言·人工智能·小程序·开源·php
sinat_255487811 小时前
第七部分。介绍MVC(模型-视图-控制器)模式
java·ide·http·tomcat·intellij-idea
Samooyou1 小时前
RAG项目案例--02在线检索&过滤流水线
人工智能·python·ai·全文检索·检索
动能小子ohhh2 小时前
DocForge平台的设计与开发--文件上传接口的实现
开发语言·人工智能·python·langchain·ocr·fastapi
朴马丁2 小时前
预制菜的“数字厨房”:PLM如何支撑菜品标准化与供应链高效协同?
大数据·人工智能·食品行业·流程行业plm
李白的天不白2 小时前
ps -ef | grep java
java
ab_dg_dp2 小时前
Android 17+ 提取 AIDL 生成 Java 文件的实用脚本
android·java·python
小沈同学呀2 小时前
SpringAI+MCPServer实战-StreamableHTTP协议打造企业级AI工具服务
人工智能·微服务架构·springai·mcpserver·javaai·streamablehttp
net3m332 小时前
一阶软件低通滤波器算法
人工智能·算法