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를 사용하면 다음과 통합할 수 있어요:
- Cursor
- Claude Desktop
- MCP Inspector
참고: MCP Inspector를 사용할 때는 CSRF와 CORS 보호를 비활성화해야 할 수 있어요.
추가 자료 (Additional Resources)
- MCP Authorization Specification
- MCP Security GitHub Repository
- Sample Applications
- MCP Authorization Specification
- Spring Security OAuth 2.0 Resource Server
- Spring Security OAuth 2.0 Client
- Spring Authorization Server