MCP 보안

MCP 보안 (MCP Security)

Spring AI MCP Security 모듈은 Model Context Protocol 구현에 OAuth 2.0과 API 키 기반 보안을 제공해요. 커뮤니티 주도 프로젝트로, MCP 서버와 클라이언트 모두를 업계 표준 인증·권한 부여 메커니즘으로 보호할 수 있게 해 줘요. 이 글에서 서버 보안, 클라이언트 보안, 인가 서버 구성을 차근차근 살펴볼게요.

출처: 문서

본문

MCP 보안 (MCP Security)

참고: 아직 진행 중인 작업이에요. 문서와 API는 향후 릴리스에서 변경될 수 있어요.

Spring AI MCP Security 모듈은 Spring AI의 Model Context Protocol 구현에 OAuth 2.0과 API 키 기반 보안을 포괄적으로 제공해요. 이 커뮤니티 주도 프로젝트는 개발자가 업계 표준 인증·권한 부여 메커니즘으로 MCP 서버와 클라이언트 모두를 보호할 수 있게 해 줘요.

참고: 이 모듈은 spring-ai-community/mcp-security 프로젝트의 일부예요. 커뮤니티 주도 프로젝트이며 아직 Spring AI나 MCP 프로젝트에서 공식적으로 보증하지 않아요. 최신 지원 Spring AI 버전은 프로젝트 저장소를 확인하세요.

개요 (Overview)

MCP Security 모듈은 세 가지 주요 컴포넌트를 제공해요:

  • MCP 서버 보안 (MCP Server Security) - Spring AI MCP 서버를 위한 OAuth 2.0 리소스 서버와 API 키 인증
  • MCP 클라이언트 보안 (MCP Client Security) - Spring AI MCP 클라이언트를 위한 OAuth 2.0 클라이언트 지원
  • MCP 인가 서버 (MCP Authorization Server) - MCP 특화 기능이 강화된 Spring Authorization Server

이 프로젝트는 개발자가 다음을 할 수 있게 해 줘요:

  • OAuth 2.0 인증과 API 키 기반 접근으로 MCP 서버 보호
  • OAuth 2.0 인가 흐름으로 MCP 클라이언트 구성
  • MCP 워크플로에 특화된 인가 서버 설정
  • MCP 도구와 리소스에 대한 세분화된 접근 제어 구현

MCP 서버 보안 (MCP Server Security)

MCP Server Security 모듈은 Spring AI의 MCP 서버에 OAuth 2.0 리소스 서버 기능을 제공해요. 또한 API 키 기반 인증에 대한 기본 지원도 제공해요.

참고: 이 모듈은 Spring WebMVC 기반 서버에서만 호환돼요.

의존성 (Dependencies)

프로젝트에 다음 의존성을 추가하세요:

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

    <!-- 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 Configuration)

기본 OAuth 2.0 설정 (Basic OAuth 2.0 Setup)

먼저 application.properties에서 MCP 서버를 활성화하세요:

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

그런 다음 제공된 MCP configurer와 함께 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();
    }
}

도구 호출만 보호하기 (Securing Tool Calls Only)

도구 호출만 보호하고 다른 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 키 인증 (API Key Authentication)

MCP Server Security 모듈은 API 키 기반 인증도 지원해요. ApiKeyEntity 객체를 저장하려면 직접 ApiKeyEntityRepository 구현을 제공해야 해요. InMemoryApiKeyEntityRepository와 기본 ApiKeyEntityImpl로 샘플 구현이 제공돼요:

참고: 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: ***
                            // 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 ***".
                            // 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....ey 헤더를 사용해 MCP 서버를 호출할 수 있어요.

알려진 제한 사항 (Known Limitations)

참고:

  • deprecated된 SSE 전송은 지원되지 않아요. Streamable HTTP 또는 stateless 전송을 사용하세요.
  • WebFlux 기반 서버는 지원되지 않아요.
  • Opaque 토큰은 지원되지 않아요. JWT를 사용하세요.

MCP 클라이언트 보안 (MCP Client Security)

MCP Client Security 모듈은 Spring AI의 MCP 클라이언트에 OAuth 2.0 지원을 제공하며, HttpClient 기반 클라이언트(spring-ai-starter-mcp-client에서)와 WebClient 기반 클라이언트(spring-ai-starter-mcp-client-webflux에서) 모두를 지원해요.

참고: 이 모듈은 McpSyncClient만 지원해요.

의존성 (Dependencies)

  • Maven
  • Gradle
<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>mcp-client-security</artifactId>
</dependency>
implementation 'org.springaicommunity:mcp-client-security'

인가 흐름 (Authorization Flows)

토큰을 얻기 위한 세 가지 OAuth 2.0 흐름이 있어요:

  • 인가 코드 흐름 (Authorization Code Flow) - 모든 MCP 요청이 사용자 요청의 맥락에서 이루어질 때의 사용자 수준 권한용
  • 클라이언트 자격 증명 흐름 (Client Credentials Flow) - 인간이 개입하지 않는 머신 투 머신 사용 사례용
  • 하이브리드 흐름 (Hybrid Flow) - 사용자 없는 상태에서 일부 작업(예: initialize, tools/list)이 일어나지만 도구 호출에는 사용자 수준 권한이 필요한 시나리오를 위해 두 흐름을 결합

참고: 사용자 수준 권한이 있고 모든 MCP 요청이 사용자 맥락에서 발생한다면 인가 코드 흐름을 사용하세요. 머신 투 머신 통신에는 클라이언트 자격 증명을 사용하세요. MCP 클라이언트 구성에 Spring Boot 프로퍼티를 사용한다면 하이브리드 흐름을 사용하세요. 도구 발견이 사용자 없이 시작 시 일어나기 때문이에요.

공통 설정 (Common Setup)

모든 흐름에서 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 기반 클라이언트 (HttpClient-Based Clients)

spring-ai-starter-mcp-client를 사용할 때는 McpSyncHttpClientRequestCustomizer 빈을 구성하세요:

@Configuration
class McpConfiguration {

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

사용 가능한 customizer:

  • OAuth2AuthorizationCodeSyncHttpRequestCustomizer - 인가 코드 흐름용
  • OAuth2ClientCredentialsSyncHttpRequestCustomizer - 클라이언트 자격 증명 흐름용
  • OAuth2HybridSyncHttpRequestCustomizer - 하이브리드 흐름용

WebClient 기반 클라이언트 (WebClient-Based Clients)

spring-ai-starter-mcp-client-webflux를 사용할 때는 MCP ExchangeFilterFunction으로 WebClient.Builder를 구성하세요:

@Configuration
class McpConfiguration {

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

사용 가능한 filter 함수:

  • McpOAuth2AuthorizationCodeExchangeFilterFunction - 인가 코드 흐름용
  • McpOAuth2ClientCredentialsExchangeFilterFunction - 클라이언트 자격 증명 흐름용
  • McpOAuth2HybridExchangeFilterFunction - 하이브리드 흐름용

Spring AI 자동 설정 우회하기 (Working Around Spring AI Autoconfiguration)

Spring AI의 자동 설정은 시작 시 MCP 클라이언트를 초기화하는데, 이는 사용자 기반 인증에 문제가 될 수 있어요. 이를 피하려면:

옵션 1: @Tool 자동 설정 비활성화 (Disable @Tool Auto-configuration)

빈 ToolCallbackResolver 빈을 게시해 Spring AI의 @Tool 자동 설정을 비활성화하세요:

@Configuration
public class McpConfiguration {

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

옵션 2: 프로그래밍 방식 클라이언트 구성 (Programmatic Client Configuration)

Spring Boot 프로퍼티 대신 MCP 클라이언트를 프로그래밍 방식으로 구성하세요. 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 = McpSchema.Implementation.builder("client-name", commonProps.getVersion()).build();

    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 = McpSchema.Implementation.builder("clientName", commonProperties.getVersion()).build();

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

알려진 제한 사항 (Known Limitations)

참고:

  • Spring WebFlux 서버는 지원되지 않아요.
  • Spring AI 자동 설정은 앱 시작 시 MCP 클라이언트를 초기화하므로 사용자 기반 인증에 우회 방법이 필요해요.
  • 서버 모듈과 달리 클라이언트 구현은 HttpClient와 WebClient 모두로 SSE 전송을 지원해요.

MCP 인가 서버 (MCP Authorization Server)

MCP Authorization Server 모듈은 Spring Security의 OAuth 2.0 Authorization Server를 MCP 인가 스펙과 관련된 기능(예: Dynamic Client Registration, Resource Indicators)으로 강화해요.

의존성 (Dependencies)

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

구성 (Configuration)

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

알려진 제한 사항 (Known Limitations)

참고:

  • Spring WebFlux 서버는 지원되지 않아요.
  • 모든 클라이언트가 모든 resource 식별자를 지원해요.

샘플과 통합 (Samples and Integrations)

samples 디렉터리에는 통합 테스트를 포함한 이 프로젝트의 모든 모듈에 대한 작동하는 예제가 있어요.

mcp-server-security와 지원 mcp-authorization-server를 사용하면 다음과 통합할 수 있어요:

참고: MCP Inspector를 사용할 때는 CSRF와 CORS 보호를 비활성화해야 할 수 있어요.

추가 자료 (Additional Resources)

더 알아보기 (Learn more)