MCP セキュリティ

これはまだ開発中の作業です。ドキュメントと API は今後のリリースで変更される可能性があります。

Spring AI MCP セキュリティモジュールは、Spring AI のモデルコンテキストプロトコル実装に対し、包括的な OAuth 2.0 および API キーベースのセキュリティサポートを提供します。このコミュニティ主導のプロジェクトにより、開発者は業界標準の認証および認可メカニズムを使用して、MCP サーバーとクライアントの両方を保護できます。

このモジュールは spring-ai-community/mcp-security [GitHub] (英語) プロジェクトの一部であり、現在は Spring AI の 1.1.x(ブランチ)でのみ動作します。これはコミュニティ主導のプロジェクトであり、Spring AI または MCP プロジェクトによる公式承認はまだ受けていません。

概要

MCP セキュリティモジュールは、次の 3 つの主要コンポーネントを提供します。

  • MCP サーバーセキュリティ - OAuth 2.0 リソースサーバーと Spring AI MCP サーバーの API キー認証

  • MCP クライアントセキュリティ - Spring AI MCP クライアント向けの OAuth 2.0 クライアントサポート

  • MCP 認証サーバー - MCP 固有の機能を備えた強化された Spring Authorization Server

このプロジェクトにより、開発者は次のことが可能になります。

  • OAuth 2.0 認証と API キーベースのアクセスによる MCP サーバーのセキュリティ保護

  • OAuth 2.0 認証フローを使用して MCP クライアントを構成する

  • MCP ワークフロー専用に設計された認可サーバーをセットアップする

  • MCP ツールとリソースに対するきめ細かなアクセス制御を実装する

MCP サーバーセキュリティ

MCP サーバーセキュリティモジュールは、Spring AI の MCP サーバーに OAuth 2.0 リソースサーバー機能を提供します。また、API キーベースの認証の基本サポートも提供します。

このモジュールは、Spring WebMVC ベースのサーバーとのみ互換性があります。

依存関係

プロジェクトに次の依存関係を追加します。

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

    <!-- OPTIONAL: For OAuth2 support -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
</dependencies>
implementation 'org.springaicommunity:mcp-server-security'
implementation 'org.springframework.boot:spring-boot-starter-security'

// OPTIONAL: For OAuth2 support
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'

OAuth 2.0 構成

基本的な OAuth 2.0 設定

まず、application.properties で MCP サーバーを有効にします。

spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLE

次に、提供されている MCP コンフィギュレーターを使用して、Spring Security の標準 API を使用してセキュリティを構成します。

@Configuration
@EnableWebSecurity
class McpServerConfiguration {

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

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // Enforce authentication with token on EVERY request
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                // Configure OAuth2 on the MCP server
                .with(
                        McpServerOAuth2Configurer.mcpServerOAuth2(),
                        (mcpAuthorization) -> {
                            // REQUIRED: the issuerURI
                            mcpAuthorization.authorizationServer(issuerUrl);
                            // OPTIONAL: enforce the `aud` claim in the JWT token.
                            // Not all authorization servers support resource indicators,
                            // so it may be absent. Defaults to `false`.
                            // See RFC 8707 Resource Indicators for OAuth 2.0
                            // https://www.rfc-editor.org/rfc/rfc8707.html
                            mcpAuthorization.validateAudienceClaim(true);
                        }
                )
                .build();
    }
}

ツール呼び出しのみを保護する

他の MCP 操作 (initialize や tools/list など) をパブリックのままにして、ツール呼び出しのみを保護するようにサーバーを構成できます。

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Enable annotation-driven security
class McpServerConfiguration {

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

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // Open every request on the server
                .authorizeHttpRequests(auth -> {
                    auth.requestMatcher("/mcp").permitAll();
                    auth.anyRequest().authenticated();
                })
                // Configure OAuth2 on the MCP server
                .with(
                        McpResourceServerConfigurer.mcpServerOAuth2(),
                        (mcpAuthorization) -> {
                            // REQUIRED: the 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 Server Security モジュールは API キーベースの認証もサポートしています。ApiKeyEntity オブジェクトを保存するには、独自の ApiKeyEntityRepository 実装を用意する必要があります。

サンプル実装は、デフォルトの ApiKeyEntityImpl とともに InMemoryApiKeyEntityRepository で利用できます。

InMemoryApiKeyEntityRepository は API キーの保存に bcrypt を使用しますが、これは計算コストが高いため、高トラフィックの本番環境での使用には適していません。本番環境では、独自の ApiKeyEntityRepository を実装してください。
@Configuration
@EnableWebSecurity
class McpServerConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
                .with(
                        mcpServerApiKey(),
                        (apiKey) -> {
                            // REQUIRED: the repo for API keys
                            apiKey.apiKeyRepository(apiKeyRepository());

                            // OPTIONAL: name of the header containing the API key.
                            // Here for example, api keys will be sent with "CUSTOM-API-KEY: <value>"
                            // Replaces .authenticationConverter(...) (see below)
                            //
                            // apiKey.headerName("CUSTOM-API-KEY");

                            // OPTIONAL: custom converter for transforming an http request
                            // into an authentication object. Useful when the header is
                            // "Authorization: Bearer <value>".
                            // Replaces .headerName(...) (see above)
                            //
                            // apiKey.authenticationConverter(request -> {
                            //     var key = extractKey(request);
                            //     return ApiKeyAuthenticationToken.unauthenticated(key);
                            // });
                        }
                )
                .build();
    }

    /**
     * Provide a repository of {@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 サーバーを呼び出すことができます。

既知の制限

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>
implementation 'org.springaicommunity:mcp-client-security'

認可フロー

トークンを取得するには、次の 3 つの OAuth 2.0 フローが利用できます。

  • 認証コードフロー - すべての MCP リクエストがユーザーリクエストのコンテキスト内で行われる場合のユーザーレベルの権限の場合

  • クライアント資格情報フロー - 人間が関与しないマシンツーマシンのユースケース向け

  • ハイブリッドフロー - 一部の操作(initialize や tools/list など)はユーザーがいなくても実行されるが、ツールの呼び出しにはユーザーレベルの権限が必要となるシナリオでは、両方のフローを組み合わせます。

ユーザーレベルの権限があり、すべての MCP リクエストがユーザーコンテキスト内で発生する場合は、認可コードフローを使用してください。マシン間通信にはクライアント資格情報を使用してください。MCP クライアント構成に Spring Boot プロパティを使用する場合は、ユーザーが存在しない起動時にツール検出が行われるため、ハイブリッドフローを使用してください。

一般的な設定

すべてのフローについて、application.properties で Spring Security の OAuth2 クライアントサポートを有効にします。

# Ensure MCP clients are sync
spring.ai.mcp.client.type=SYNC

# For authorization_code or hybrid flow
spring.security.oauth2.client.registration.authserver.client-id=<THE CLIENT ID>
spring.security.oauth2.client.registration.authserver.client-secret=<THE CLIENT SECRET>
spring.security.oauth2.client.registration.authserver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.authserver.provider=authserver

# For client_credentials or hybrid flow
spring.security.oauth2.client.registration.authserver-client-credentials.client-id=<THE CLIENT ID>
spring.security.oauth2.client.registration.authserver-client-credentials.client-secret=<THE 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

# Authorization server configuration
spring.security.oauth2.client.provider.authserver.issuer-uri=<THE ISSUER URI OF YOUR AUTH SERVER>

次に、OAuth2 クライアント機能を有効にする構成クラスを作成します。

@Configuration
@EnableWebSecurity
class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // in this example, the client app has no security on its endpoints
                .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
                // turn on OAuth2 support
                .oauth2Client(Customizer.withDefaults())
                .build();
    }
}

HttpClient ベースのクライアント

spring-ai-starter-mcp-client を使用する場合は、McpSyncHttpClientRequestCustomizer Bean を構成します。

@Configuration
class McpConfiguration {

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

    @Bean
    McpSyncHttpClientRequestCustomizer requestCustomizer(
            OAuth2AuthorizedClientManager clientManager
    ) {
        // The clientRegistration name, "authserver",
        // must match the name in application.properties
        return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(
                clientManager,
                "authserver"
        );
    }
}

利用可能なカスタマイザー:

  • OAuth2AuthorizationCodeSyncHttpRequestCustomizer - 認証コードフローの場合

  • OAuth2ClientCredentialsSyncHttpRequestCustomizer - クライアント資格情報フローの場合

  • OAuth2HybridSyncHttpRequestCustomizer - ハイブリッドフローの場合

Web クライアントベースのクライアント

spring-ai-starter-mcp-client-webflux を使用する場合は、MCP ExchangeFilterFunction を使用して WebClient.Builder を構成します。

@Configuration
class McpConfiguration {

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

    @Bean
    WebClient.Builder mcpWebClientBuilder(OAuth2AuthorizedClientManager clientManager) {
        // The clientRegistration name, "authserver", must match the name in 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(
        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 ベースのクライアントの場合:

@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();
}

次に、クライアントをチャットクライアントに追加します。

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

既知の制限

  • Spring WebFlux サーバーはサポートされていません。

  • Spring AI 自動構成は、アプリの起動時に MCP クライアントを初期化するため、ユーザーベースの認証のための回避策が必要になります。

  • サーバーモジュールとは異なり、クライアント実装は HttpClient と WebClient の両方を使用した SSE トランスポートをサポートします。

MCP 認証サーバー

MCP 認可サーバーモジュールは、動的クライアント登録やリソースインジケーターなどの MCP 認証仕様 (英語) に関連する機能で Spring Security の OAuth 2.0 認可サーバーを拡張します。

依存関係

  • Maven

  • Gradle

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

構成

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:
      # A single user, named "user"
      name: user
      password: password

server:
  servlet:
    session:
      cookie:
        # Override the default cookie name (JSESSIONID).
        # This allows running multiple Spring apps on localhost, and they'll each have their own cookie.
        # Otherwise, since the cookies do not take the port into account, they are confused.
        name: MCP_AUTHORIZATION_SERVER_SESSIONID

次に、セキュリティフィルターチェーンを使用して認可サーバーの機能をアクティブ化します。

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            // all requests must be authenticated
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            // enable authorization server customizations
            .with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())
            // enable form-based login, for user "user"/"password"
            .formLogin(withDefaults())
            .build();
}

既知の制限

  • Spring WebFlux サーバーはサポートされていません。

  • すべてのクライアントはすべての resource 識別子をサポートします。

サンプルと統合

サンプルディレクトリに [GitHub] (英語) は、統合テストを含む、このプロジェクト内のすべてのモジュールの実際の例が含まれています。

mcp-server-security とそれをサポートする mcp-authorization-server を使用すると、次のものと統合できます。

MCP インスペクション官 (英語) を使用する場合、CSRF および CORS 保護を無効にする必要がある場合があります。