MCP 安全
| 此功能仍在開發中。文件和 API 在未來的版本中可能會有所更改。 |
Spring AI MCP 安全模組為 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 授權伺服器 - 增強型 Spring 授權伺服器,具有 MCP 特定功能
該專案使開發人員能夠
-
使用 OAuth 2.0 身份驗證和基於 API 金鑰的訪問來保護 MCP 伺服器
-
使用 OAuth 2.0 授權流程配置 MCP 客戶端
-
設定專為 MCP 工作流設計的授權伺服器
-
為 MCP 工具和資源實施細粒度訪問控制
MCP 伺服器安全
MCP 伺服器安全模組為 Spring AI 的 MCP 伺服器 提供 OAuth 2.0 資源伺服器功能。它還為基於 API 金鑰的身份驗證提供基本支援。
| 此模組僅與基於 Spring WebMVC 的伺服器相容。 |
依賴關係
將以下依賴項新增到您的專案中
-
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 設定
首先,在 application.properties 中啟用 MCP 伺服器
spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLE
然後,使用 Spring Security 的標準 API 和提供的 MCP 配置器配置安全性
@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();
}
}
然後,使用 @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 伺服器。
MCP 客戶端安全
MCP 客戶端安全模組為 Spring AI 的 MCP 客戶端 提供 OAuth 2.0 支援,支援基於 HttpClient 的客戶端(來自 spring-ai-starter-mcp-client)和基於 WebClient 的客戶端(來自 spring-ai-starter-mcp-client-webflux)。
此模組僅支援 McpSyncClient。 |
依賴關係
-
Maven
-
Gradle
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-client-security</artifactId>
</dependency>
implementation 'org.springaicommunity:mcp-client-security'
授權流程
有三種 OAuth 2.0 流程可用於獲取令牌
-
授權碼流程 - 當每個 MCP 請求都在使用者請求的上下文中發出時,用於使用者級別許可權
-
客戶端憑據流程 - 用於機器對機器用例,其中沒有人機互動
-
混合流程 - 結合兩種流程,適用於某些操作(如
initialize或tools/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 ExchangeFilterFunction 的 WebClient.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();
MCP 授權伺服器
MCP 授權伺服器模組增強了 Spring Security 的 OAuth 2.0 授權伺服器,增加了與 MCP 授權規範 相關的特性,例如動態客戶端註冊和資源指示器。
依賴項
-
Maven
-
Gradle
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-authorization-server</artifactId>
</dependency>
implementation 'org.springaicommunity:mcp-authorization-server'
配置
在 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"
- "https://:8080/authorize/oauth2/code/authserver"
# mcp-inspector
- "https://: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();
}
示例和整合
示例目錄 包含此專案中所有模組的工作示例,包括整合測試。
透過 mcp-server-security 和支援的 mcp-authorization-server,您可以與以下工具整合
-
Cursor
-
Claude Desktop
| 使用 MCP Inspector 時,您可能需要停用 CSRF 和 CORS 保護。 |