跳到主要内容
版本:2.0.1

MCP 安全 :: Spring AI 参考

MCP 安全

备注

此功能仍在开发中。相关文档和 API 可能会在未来版本中发生变更。

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

备注

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

概述​

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

  • MCP Server 安全 - 针对 Spring AI MCP 服务器的 OAuth 2.0 资源服务器和 API key 认证
  • MCP Client 安全 - 针对 Spring AI MCP 客户端的 OAuth 2.0 客户端支持
  • MCP 授权服务器 - 具备 MCP 特定功能的增强版 Spring Authorization Server

该项目使开发人员能够:

  • 使用 OAuth 2.0 认证和基于 API 密钥的访问方式保护 MCP 服务器

  • 配置 MCP 客户端以支持 OAuth 2.0 授权流程

  • 搭建专为 MCP 工作流设计的授权服务器

  • 为 MCP 工具和资源配置细粒度访问控制

MCP 服务器安全性​

MCP Server Security 模块为 Spring AI 的 MCP servers 提供 OAuth 2.0 资源服务器能力。它还提供了基于 API-key 认证的基础支持。

important

此模块仅兼容基于 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 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();
}
}

仅保护工具调用​

您可以配置服务器,仅对工具调用进行安全保护,而其他 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();
}
}

然后,使用 方法安全(method security) 和 @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 Key 认证​

MCP Server Security 模块还支持基于 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 服务器。

已知限制​

important
  • 不支持已弃用的 SSE 传输方式。请使用 Streamable HTTP 或 无状态传输。

  • 不支持基于 WebFlux 的服务器。

  • 不支持不透明令牌(Opaque tokens)。请使用 JWT。

MCP 客户端安全​

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

important

此模块仅支持 McpSyncClient。

依赖项​

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

授权流程​

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

  • 授权码流程(Authorization Code Flow) - 当每个 MCP 请求都在用户请求的上下文中发起时,用于用户级别的权限
  • 客户端凭证流程(Client Credentials Flow) - 用于没有人类参与的系统间(machine-to-machine)用例
  • 混合流程(Hybrid Flow) - 结合上述两种流程,适用于部分操作(如 initialize 或 tools/list)在无用户在场时发生,但工具调用需要用户级别权限的场景
提示

当您需要用户级权限且所有 MCP 请求都在用户上下文中发生时,请使用授权码(authorization code)流程。对于机器间通信,请使用客户端凭证(client credentials)。在使用 Spring Boot 属性配置 MCP 客户端时,请使用混合(hybrid)流程,因为在没有用户存在的启动阶段,工具发现(tool discovery)就已经发生。

通用设置​

在所有流程中,都在你的 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
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"
);
}
}

可用定制器:

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

基于 WebClient 的客户端​

使用 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"
)
);
}
}

可用的过滤函数:

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

已知限制​

important
  • 不支持 Spring WebFlux 服务器。

  • Spring AI 自动配置会在应用启动时初始化 MCP 客户端,针对基于用户的身份验证需要采取变通方案。

  • 与服务器模块不同,客户端实现同时支持 HttpClient 和 WebClient 的 SSE 传输。

MCP 授权服务器​

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

依赖项​

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

已知限制​

important
  • 不支持 Spring WebFlux 服务器。

  • 所有客户端均支持所有的 resource 标识符。

示例与集成​

samples 目录包含了本项目中所有模块的可用示例,包括集成测试。

借助 mcp-server-security 以及配套的 mcp-authorization-server,您可以集成:

备注

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

其他资源​