1. 概述
模型上下文協議 (MCP) 允許人工智能模型通過安全的 API 訪問業務數據。 當我們構建處理敏感信息的 MCP 服務器時,需要適當的授權來控制誰可以訪問哪些數據。
OAuth2 提供了與 MCP 系統配合良好的基於令牌的安全機制。 相反於構建自定義身份驗證,我們可以使用 OAuth2 標準來保護我們的 MCP 服務器並管理客户端訪問。
在本文中,我們將探討如何使用 Spring AI 和 OAuth2 來安全地保護 MCP 服務器和客户端。 我們將構建一個包含三個組件的完整示例:一個授權服務器、一個帶有計算器工具的受保護的 MCP 服務器以及一個處理用户和系統請求的客户端。
2. MCP 安全架構
為了安全地保護 MCP 服務器,理解我們將如何在 MCP 服務器之前集成授權服務器至關重要。
我們的系統包含一個授權服務器,用於頒發帶有適當權限的 JWT 令牌,以及一個 MCP 服務器,用於驗證令牌並控制訪問計算器工具。 此外,還有一個 MCP 客户端,用於獲取令牌並管理不同請求類型的身份驗證:
MCP 服務器充當 OAuth2 資源服務器。它們在處理任何操作之前,會在請求標頭中檢查 JWT 令牌。 這將安全問題與業務邏輯分離。客户端從 OAuth2 授權服務器獲取訪問令牌。然後,客户端將這些令牌包含在 MCP 請求中。最後,MCP 服務器在允許操作之前驗證令牌。 這樣,我們的組件將協同工作:
3. 構建授權服務器
我們將首先構建授權服務器,因為其他組件依賴於它。
3.1. 添加依賴
我們需要添加 OAuth2 授權服務器 依賴才能使用它:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>3.2. 配置授權服務器
在 application.yml 中配置服務器。
server:
port: 9000
spring:
security:
user:
name: user
password: password
oauth2:
authorizationserver:
client:
oidc-client:
registration:
client-id: "mcp-client"
client-secret: "{noop}mcp-secret"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "client_credentials"
- "refresh_token"
redirect-uris:
- "http://localhost:8080/authorize/oauth2/code/authserver"
scopes:
- "openid"
- "profile"
- "calc.read"
- "calc.write"這將在 9000 端口上設置一個授權服務器,該服務器支持使用授權碼流程(用於用户)和憑據流程(用於系統)。
4. 加密 MCP 服務器
現在我們將創建一個需要 OAuth2 令牌並提供計算工具的 MCP 服務器。
4.1. 配置 MCP 服務器的依賴項
現在,讓我們添加對 OAuth2 資源服務器 的支持:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>1.0.0-M7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>4.2. 服務器配置
讓我們配置 MCP 服務器作為 OAuth2 資源服務器:
server.port=8090
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9000

spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.name=mcp-calculator-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.stdio=falseSpring Boot 在我們設置 issuer URI 時會自動處理 JWT 驗證。 現在,所有對我們 MCP 服務器的請求都需要在 Authorization 標頭中包含有效的 JWT 令牌。
4.3. 創建 MCP 工具
LLM 通常在數學方面表現不佳。因此,我們需要為它們提供訪問工具,以便根據請求計算結果:
@Tool(description = "Add two numbers")
public CalculationResult add(
@ToolParam(description = "First number") double a,
@ToolParam(description = "Second number") double b) {
double result = a + b;
return new CalculationResult("addition", a, b, result);
}
@Tool(description = "Multiply two numbers")
public CalculationResult multiply(
@ToolParam(description = "First number") double a,
@ToolParam(description = "Second number") double b) {
double result = a * b;
return new CalculationResult("multiplication", a, b, result);
}安全配置自動保護所有 MCP 工具。 未經有效令牌的請求將被拒絕。這些工具會被添加到上下文中,並在每次用户查詢時提供給 LLM。然後,LLM 會決定為用户查詢使用哪個工具來響應。
5. 構建 MCP 客户端
現在,我們需要客户端來處理最複雜的部分,因為它需要同時處理用户請求和系統初始化。
5.1. 客户端依賴項
我們首先添加 mcp-client 和 oauth2-client 依賴項:
org.springframework.ai/spring-ai-starter-mcp-client-webflux 和 org.springframework.boot/spring-boot-starter-oauth2-client。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>5.2. 客户端配置
現在,我們需要在 application.properties 中註冊兩個 OAuth2 客户端:
server.port=8080
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8090
spring.ai.mcp.client.type=SYNC
spring.security.oauth2.client.provider.authserver.issuer-uri=http://localhost:9000
# OAuth2 Client for User-Initiated Requests (Authorization Code Grant)
spring.security.oauth2.client.registration.authserver.client-id=mcp-client
spring.security.oauth2.client.registration.authserver.client-secret=mcp-secret
spring.security.oauth2.client.registration.authserver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.authserver.provider=authserver
spring.security.oauth2.client.registration.authserver.scope=openid,profile,mcp.read,mcp.write
spring.security.oauth2.client.registration.authserver.redirect-uri={baseUrl}/authorize/oauth2/code/{registrationId}
# OAuth2 Client for Machine-to-Machine Requests (Client Credentials Grant)
spring.security.oauth2.client.registration.authserver-client-credentials.client-id=mcp-client
spring.security.oauth2.client.registration.authserver-client-credentials.client-secret=mcp-secret
spring.security.oauth2.client.registration.authserver-client-credentials.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.authserver-client-credentials.provider=authserver
spring.security.oauth2.client.registration.authserver-client-credentials.scope=mcp.read,mcp.write
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}我們需要為不同的認證流程分別進行兩次註冊。 authserver 註冊使用授權碼流程,用於用户發起請求。 authserver-client-credentials 註冊使用客户端憑據流程,用於系統啓動。
5.3. 安全配置
現在,讓我們設置 Spring Security 以處理 OAuth2:
@Bean
WebClient.Builder webClientBuilder(McpSyncClientExchangeFilterFunction filterFunction) {
return WebClient.builder()
.apply(filterFunction.configuration());
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.oauth2Client(Customizer.withDefaults())
.csrf(CsrfConfigurer::disable)
.build();
}5.4. 選擇合適的 Token
整個挑戰的核心在於為每個請求選擇合適的 Token。我們需要一個自定義的 ExchangeFilterFunction 實現,該實現能夠檢測請求上下文:
@Component
public class McpSyncClientExchangeFilterFunction implements ExchangeFilterFunction {
private final ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialTokenProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
private final ServletOAuth2AuthorizedClientExchangeFilterFunction delegate;
private final ClientRegistrationRepository clientRegistrationRepository;
private static final String AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID = "authserver";
private static final String CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID = "authserver-client-credentials";
public McpSyncClientExchangeFilterFunction(OAuth2AuthorizedClientManager clientManager,
ClientRegistrationRepository clientRegistrationRepository) {
this.delegate = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientManager);
this.delegate.setDefaultClientRegistrationId(AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID);
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes) {
return this.delegate.filter(request, next);
}
else {
var accessToken = getClientCredentialsAccessToken();
var requestWithToken = ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(accessToken))
.build();
return next.exchange(requestWithToken);
}
}
private String getClientCredentialsAccessToken() {
var clientRegistration = this.clientRegistrationRepository
.findByRegistrationId(CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID);
var authRequest = OAuth2AuthorizationContext.withClientRegistration(clientRegistration)
.principal(new AnonymousAuthenticationToken("client-credentials-client", "client-credentials-client",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")))
.build();
return this.clientCredentialTokenProvider.authorize(authRequest).getAccessToken().getTokenValue();
}
public Consumer<WebClient.Builder> configuration() {
return builder -> builder.defaultRequest(this.delegate.defaultRequest()).filter(this);
}
}過濾器檢查是否存在活動 Web 請求。 如果存在,它將使用用户的授權代碼令牌。如果不存在(例如在應用啓動期間),則使用客户端憑據。
6. 使用安全MCP系統
現在我們已經完成了所有組件的覆蓋,接下來讓我們看看如何有效地使用安全MCP系統。
6.1. 創建 ChatClient
讓我們用 ChatClient 將所有內容連接起來:
@Bean
ChatClient chatClient(ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpClients) {
return chatClientBuilder.defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpClients))
.build();
}6.2. 發送請求
現在我們可以正常使用 ChatClient。 安全性會自動處理:
@GetMapping("/calculate")
public String calculate(@RequestParam String expression, @RegisteredOAuth2AuthorizedClient("authserver") OAuth2AuthorizedClient authorizedClient) {
String prompt = String.format("Please calculate the following mathematical expression using the available calculator tools: %s", expression);
return chatClient.prompt()
.user(prompt)
.call()
.content();
}在啓動過程中,MCP客户端初始化使用客户端憑據令牌。當用户通過Web界面發起請求時,它使用他們的授權碼令牌。
7. 驗證配置
為了理解應用程序的工作原理,我們需要查看其產生的結果。 在啓動任何應用程序之前,必須設置 LLM 所需的環境變量。 配置完成後,首先啓動授權服務器,該服務器監聽端口 9000。 這非常重要,因為所有其他模塊都依賴於授權服務器。 然後啓動 MCP 服務器,該服務器監聽端口 8090,最後啓動 MCP 客户端,該客户端監聽端口 8080。
測試完整流程變得簡單明瞭。 我們需要訪問 MCP 客户端端點,然後嘗試執行以下操作:
http://{base_url}:8080/calculate?expression=15+25客户端將從授權服務器獲取一個令牌,然後通過該令牌調用 MCP 服務器,並返回計算結果。我們需要確保我們使用配置文件的指定憑據登錄到授權服務器。
8. 結論
在本教程中,我們探討了 OAuth2 如何通過基於令牌的授權為 MCP 系統提供強大的安全保障。Spring Security 的 OAuth2 支持能夠實現良好的保護,同時配置量極少。通過分離授權服務器、MCP 服務器和 MCP 客户端,我們構建了一個架構,其中每個組件專注於自身的職責,從而提供了靈活性。