【Spring AI MCP】十、SpringAI MCP 安全(Security)
Spring AI MCP安全模块为开发者提供了基于OAuth 2.0和API密钥的安全解决方案,保护MCP服务器和客户端的通信安全。该社区驱动项目包含三大核心组件:服务器安全、客户端安全和授权服务器,支持细粒度的访问控制。开发者可以配置OAuth 2.0资源服务器,通过注解实现方法级安全控制,并从安全上下文中获取认证信息。该模块目前仅兼容Spring AI 1.1.x版本,属于社区项目,尚未获得
Spring AI MCP学习目录
一、MCP 原理详解
二、MCP 客户端
三、MCP 客户端
四、MCP 服务端
五、SpringAI MCP 服务端
六、SpringAI MCP 服务端 STDIO & SSE
七、SpringAI MCP 服务端 Streamable-HTTP
八、SpringAI MCP 服务端 Stateless Streamable-HTTP
九、 MCP 安全(Security)
十、SpringAI MCP 安全(Security)
十一、SpringAI MCP 客户端注解
十二、SpringAI MCP 服务端注解
十三、SpringAI MCP 特殊参数(Special Parameters)
Spring AI MCP安全模块为Spring AI中的模型上下文协议实现提供了全面的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服务器
- 为MCP客户端配置OAuth 2.0授权流程
- 专门为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>
OAuth 2.0
1、开启MCP
spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLE
2、配置
@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();
}
}
工具调用保护
可配置服务器仅对工具调用(如initialize和tools/list等操作)进行安全保护,其他MCP操作保持公开访问。
@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 Key
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服务器。
MCP 客户端安全
MCP客户端安全模块为Spring AI的MCP客户端提供OAuth 2.0支持,包括基于HttpClient的客户端(来自spring-ai-starter-mcp-client)和基于WebClient的客户端(来自spring-ai-starter-mcp-client-webflux)。
maven
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-client-security</artifactId>
</dependency>
授权流程
提供三种OAuth 2.0令牌获取方式:
- 授权码流程:适用于用户级权限场景,所有MCP请求均在用户请求上下文中发起
- 客户端凭证流程:适用于机器间通信的无人工参与场景
- 混合流程:结合前两种流程,适用于部分操作(如初始化或工具列表)无需用户参与,但工具调用需要用户级权限的场景
使用建议:
用户级权限且所有MCP请求在用户上下文中发起时,使用授权码流程
机器间通信时使用客户端凭证流程
使用Spring Boot属性配置MCP客户端时(工具发现需在启动时完成且无用户参与),采用混合流程
通用配置
application.properties
# 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>
配置类
@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-Based Clients
@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"
);
}
}
WebClient-Based Clients
@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"
)
);
}
}
以下是Spring AI自动配置的解决方案翻译:
规避Spring AI自动配置问题
Spring AI的自动配置会在应用启动时初始化MCP客户端,这可能对基于用户的认证造成问题。为避免此情况:
方案一:禁用@Tool自动配置
通过注册空的ToolCallbackResolver Bean来禁用Spring AI的@Tool自动配置:
@Configuration
public class McpConfiguration {
@Bean
ToolCallbackResolver resolver() {
return new StaticToolCallbackResolver(List.of());
}
}
方案二:编程式客户端配置
改用编程方式配置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("提示LLM执行操作")
.toolCallbacks(new SyncMcpToolCallbackProvider(mcpClient1, mcpClient2, mcpClient3))
.call()
.content();
已知限制
- 不支持Spring WebFlux服务器
- Spring AI自动配置会在应用启动时初始化MCP客户端,需要额外处理用户认证问题
- 与服务器模块不同,客户端实现支持HttpClient和WebClient的SSE传输
MCP 服务端安全
maven
<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();
}
更多推荐



所有评论(0)