Spring AI
参考
模型上下文协议(MCP)
MCP 安全(制定中)
MCP 安全

本文档仍在制定中。文档和 API 在未来的版本中可能会发生变化。

Spring AI MCP 安全模块为 Spring AI 中的模型上下文协议实现提供了全面的基于 OAuth 2.0 和 API 密钥的安全支持。这个社区驱动的项目使开发者能够使用行业标准的身份验证和授权机制来保护 MCP 服务器和客户端。

该模块是 spring-ai-community/mcp-security 项目的一部分。这是一个社区驱动的项目,尚未得到 Spring AI 或 MCP 项目的官方认可。请查看项目仓库以获取最新支持的 Spring AI 版本。

概述
MCP 安全模块提供三个主要组件:

  • MCP 服务器安全 - 为 Spring AI MCP 服务器提供 OAuth 2.0 资源服务器和 API 密钥身份验证。
  • MCP 客户端安全 - 为 Spring AI MCP 客户端提供 OAuth 2.0 客户端支持。
  • MCP 授权服务器 - 增强了 Spring Authorization Server,使其具备 MCP 特有功能。

该项目使开发者能够:

  • 使用 OAuth 2.0 身份验证和基于 API 密钥的访问来保护 MCP 服务器。
  • 使用 OAuth 2.0 授权流程配置 MCP 客户端。
  • 设置专门为 MCP 工作流设计的授权服务器。
  • 为 MCP 工具和资源实施细粒度的访问控制。

MCP 服务器安全
MCP 服务器安全模块为 Spring AI 的 MCP 服务器提供 OAuth 2.0 资源服务器功能。它还提供对基于 API 密钥的身份验证的基本支持。

该模块仅与基于 Spring WebMVC 的服务器兼容。
依赖项
将以下依赖项添加到您的项目中:

Maven

Gradle

<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>

OAuth 2.0 配置
基本 OAuth 2.0 设置
首先,在您的 application.properties 中启用 MCP 服务器:

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

然后,使用 Spring Security 的标准 API 和提供的 MCP 配置器来配置安全设置:

@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())
                // 在 MCP 服务器上配置 OAuth2
                .with(
                        McpServerOAuth2Configurer.mcpServerOAuth2(),
                        (mcpAuthorization) -> {
                            // 必需:issuerURI
                            mcpAuthorization.authorizationServer(issuerUrl);
                            // 可选:在 JWT 令牌中强制检查 `aud` 声明。
                            // 并非所有授权服务器都支持资源指示符,
                            // 因此它可能不存在。默认为 `false`。
                            // 参见 RFC 8707 OAuth 2.0 的资源指示符
                            // https://www.rfc-editor.org/rfc/rfc8707.html
                            mcpAuthorization.validateAudienceClaim(true);
                        }
                )
                .build();
    }
}

仅保护工具调用
您可以将服务器配置为仅保护工具调用,同时使其他 MCP 操作(如 initialize 和 tools/list)公开:

@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 注解和方法安全来控制您的工具调用:

@Service
public class MyToolsService {

    @PreAuthorize("isAuthenticated()")
    @McpTool(name = "greeter", description = "A tool that greets you, in the selected language")
    public String greet(
            @ToolParam(description = "The language for the greeting (example: english, french, ...)") String language
    ) {
        if (!StringUtils.hasText(language)) {
            language = "";
        }
        return switch (language.toLowerCase()) {
            case "english" -> "Hello you!";
            case "french" -> "Salut toi!";
            default -> "I don't understand language \"%s\". So I'm just going to say Hello!".formatted(language);
        };
    }
}

您也可以直接在工具方法中使用 SecurityContextHolder 来访问当前的身份验证信息:

@McpTool(name = "greeter", description = "A tool that greets the user by name, in the selected language")
@PreAuthorize("isAuthenticated()")
public String greet(
        @ToolParam(description = "The language for the greeting (example: 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 -> ("I don't understand language \"%s\". " +
                    "So I'm just going to say Hello %s!").formatted(language, name);
    };
}

API 密钥身份验证
MCP 服务器安全模块也支持基于 API 密钥的身份验证。您需要提供您自己的 ApiKeyEntityRepository 实现来存储 ApiKeyEntity 对象。

InMemoryApiKeyEntityRepository 附带一个默认的 ApiKeyEntityImpl 实现供您参考:

InMemoryApiKeyEntityRepository 使用 bcrypt 存储 API 密钥,这在计算上是昂贵的。它不适合高流量的生产环境。对于生产环境,请实现您自己的 ApiKeyEntityRepository

@Configuration
@EnableWebSecurity
class McpServerConfiguration {

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

                            // 可选:包含 API 密钥的头部名称。
                            // 例如,API 密钥将通过 "CUSTOM-API-KEY: <value>" 发送
                            // 替代 .authenticationConverter(...)(见下文)
                            //
                            // apiKey.headerName("CUSTOM-API-KEY");

                            // 可选:用于将 HTTP 请求转换为身份验证对象的自定义转换器。
                            // 当头部是 "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 服务器。

已知限制

  • 已弃用的 SSE 传输方式不受支持。请使用 Streamable HTTP 或无状态传输。
  • 基于 WebFlux 的服务器不受支持。
  • 不透明令牌不受支持。请使用 JWT。

MCP 客户端安全
MCP 客户端安全模块为 Spring AI 的 MCP 客户端提供 OAuth 2.0 支持,支持基于 HttpClient 的客户端(来自 spring-ai-starter-mcp-client)和基于 WebClient 的客户端(来自 spring-ai-starter-mcp-client-webflux)。

该模块仅支持 McpSyncClient
依赖项
Maven

Gradle

<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>mcp-client-security</artifactId>
</dependency>

授权流程
有三种 OAuth 2.0 流程可用于获取令牌:

  • 授权码流程 (Authorization Code Flow) - 用于用户级权限,每个 MCP 请求都在用户请求的上下文中进行。
  • 客户端凭证流程 (Client Credentials Flow) - 用于无人介入的机器对机器(M2M)用例。
  • 混合流程 (Hybrid Flow) - 结合了以上两种流程,适用于某些操作(如 initialize 或 tools/list)在无用户状态下发生,但工具调用需要用户级权限的场景。

当您拥有用户级权限且所有 MCP 请求都在用户上下文中发生时,请使用授权码流程。对于机器对机器通信,请使用客户端凭证。当使用 Spring Boot 属性配置 MCP 客户端时,请使用混合流程,因为工具发现会在启动时发生,此时没有用户存在。

通用设置
对于所有流程,在您的 application.properties 中激活 Spring Security 的 OAuth2 客户端支持:

# 确保 MCP 客户端是同步的
spring.ai.mcp.client.type=SYNC

# 对于 authorization_code 或 hybrid 流程
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

# 对于 client_credentials 或 hybrid 流程
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 客户端功能:

@Configuration
@EnableWebSecurity
class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // 在此示例中,客户端应用程序对其端点没有安全要求
                .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
                // 开启 OAuth2 支持
                .oauth2Client(Customizer.withDefaults())
                .build();
    }
}

基于 HttpClient 的客户端
当使用 spring-ai-starter-mcp-client 时,配置一个 McpSyncHttpClientRequestCustomizer bean:

@Configuration
class McpConfiguration {

    @Bean
    McpCustomizer<McpClient.SyncSpec> syncClientCustomizer() {
        return (name, syncSpec) ->
                syncSpec.transportContextProvider(
                        new AuthenticationMcpTransportContextProvider()
                );
    }

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

可用的自定义器:

  • OAuth2AuthorizationCodeSyncHttpRequestCustomizer - 用于授权码流程
  • OAuth2ClientCredentialsSyncHttpRequestCustomizer - 用于客户端凭证流程
  • OAuth2HybridSyncHttpRequestCustomizer - 用于混合流程

基于 WebClient 的客户端
当使用 spring-ai-starter-mcp-client-webflux 时,配置一个带有 MCP ExchangeFilterFunctionWebClient.Builder

@Configuration
class McpConfiguration {

    @Bean
    McpCustomizer<McpClient.SyncSpec> 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"
                )
        );
    }
}

可用的过滤器函数:

  • McpOAuth2AuthorizationCodeExchangeFilterFunction - 用于授权码流程
  • McpOAuth2ClientCredentialsExchangeFilterFunction - 用于客户端凭证流程
  • McpOAuth2HybridExchangeFilterFunction - 用于混合流程

绕过 Spring AI 自动配置
Spring AI 的自动配置会在启动时初始化 MCP 客户端,这可能会给基于用户的身份验证带来问题。为避免此问题:

选项 1:禁用 @Tool 自动配置
通过发布一个空的 ToolCallbackResolver bean 来禁用 Spring AI 的 @Tool 自动配置:

@Configuration
public class McpConfiguration {

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

选项 2:编程式客户端配置
通过编程方式配置 MCP 客户端,而不是使用 Spring Boot 属性。对于基于 HttpClient 的客户端:

@Bean
McpSyncClient client(
        JsonMapper jsonMapper,
        McpSyncHttpClientRequestCustomizer requestCustomizer,
        McpClientCommonProperties commonProps
) {
    var transport = HttpClientStreamableHttpTransport.builder(mcpServerUrl)
            .clientBuilder(HttpClient.newBuilder())
            .jsonMapper(new JacksonMcpJsonMapper(jsonMapper))
            .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 的客户端:

@Bean
McpSyncClient client(
        WebClient.Builder mcpWebClientBuilder,
        JsonMapper jsonMapper,
        McpClientCommonProperties commonProperties
) {
    var builder = mcpWebClientBuilder.baseUrl(mcpServerUrl);
    var transport = WebClientStreamableHttpTransport.builder(builder)
            .jsonMapper(new JacksonMcpJsonMapper(jsonMapper))
            .build();

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

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

然后将客户端添加到您的聊天客户端:

var chatResponse = chatClient.prompt("Prompt the LLM to do the thing")
        .tools(new SyncMcpToolCallbackProvider(
                mcpClient1, mcpClient2, mcpClient3))
        .call()
        .content();

已知限制

  • Spring WebFlux 服务器不受支持。
  • Spring AI 自动配置在应用启动时初始化 MCP 客户端,这需要为基于用户的身份验证采取变通方法。
  • 与服务器模块不同,客户端实现支持 SSE 传输,同时适用于 HttpClient 和 WebClient。

MCP 授权服务器
MCP 授权服务器模块增强了 Spring Security 的 OAuth 2.0 授权服务器,使其具备与 MCP 授权规范相关的功能,例如动态客户端注册和资源指示符。

依赖项
Maven

Gradle

<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>mcp-authorization-server</artifactId>
</dependency>

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

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 应用,并且它们各自拥有自己的 cookie。
        # 否则,由于 cookie 不考虑端口,它们会被混淆。
        name: MCP_AUTHORIZATION_SERVER_SESSIONID

然后使用安全过滤器链激活授权服务器功能:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            // 所有请求都必须经过身份验证
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            // 启用授权服务器自定义
            .with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())
            // 启用基于表单的登录,用户 "user"/"password"
            .formLogin(withDefaults())
            .build();
}

已知限制

  • Spring WebFlux 服务器不受支持。
  • 每个客户端都支持所有资源标识符。

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

结合 mcp-server-security 和支持的 mcp-authorization-server,您可以与以下工具集成:

  • Cursor
  • Claude Desktop
  • MCP Inspector

当使用 MCP Inspector 时,您可能需要禁用 CSRF 和 CORS 保护。
其他资源

  • MCP 授权规范
  • MCP 安全 GitHub 仓库
  • 示例应用
  • MCP 授权规范
  • Spring Security OAuth 2.0 资源服务器
  • Spring Security OAuth 2.0 客户端
  • Spring Authorization Server
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐