跳到主要内容

MCP 安全

Deepseek 3.2 中英对照 MCP Security (WIP) MCP Security

备注

目前仍处于开发阶段。文档和 API 在未来版本中可能发生变化。

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

备注

此模块属于 spring-ai-community/mcp-security 项目,目前仅适用于 Spring AI 的 1.1.x 分支。这是一个社区驱动的项目,尚未获得 Spring AI 或 MCP 项目的官方认可。

概述

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

  • MCP 服务器安全 - 为 Spring AI MCP 服务器提供 OAuth 2.0 资源服务器和 API 密钥认证

  • MCP 客户端安全 - 为 Spring AI MCP 客户端提供 OAuth 2.0 客户端支持

  • MCP 授权服务器 - 具备 MCP 特定功能的增强型 Spring 授权服务器

该项目使开发者能够:

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

MCP 服务器安全性

MCP Server Security 模块为 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>

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 操作(如 initializetools/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 服务器安全模块同样支持基于 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) -> {
// 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 服务器。

已知限制

:::重要

  • 已弃用的SSE传输方式不再支持。请使用可流式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
:::

依赖项

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

授权流程

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

  • 授权码流程 - 适用于每次 MCP 请求都在用户请求上下文中进行的用户级权限场景

  • 客户端凭据流程 - 适用于无需人工介入的机器对机器用例

  • 混合流程 - 结合两种流程,适用于某些操作(如 initializetools/list)在无用户参与时执行,但工具调用需要用户级权限的场景

提示

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

通用设置

对于所有流程,在 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 - 用于混合流程

基于 WebClient 的客户端

使用 spring-ai-starter-mcp-client-webflux 时,配置一个带有 MCP ExchangeFilterFunctionWebClient.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 客户端,这需要变通方法来实现基于用户的认证。

  • 与服务器模块不同,客户端实现支持使用 HttpClientWebClient 的 SSE 传输。
    :::

MCP 授权服务器

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

依赖项

<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:
# 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 标识符。
    :::

示例与集成

samples目录包含本项目所有模块的运行示例,包括集成测试。

通过 mcp-server-security 和配套的 mcp-authorization-server,你可以实现与以下系统的集成:

备注

在使用 MCP Inspector 时,你可能需要禁用 CSRF 和 CORS 保护。

其他资源