使用 Spring AI 和 OAuth2 進行 MCP 授權
1.概述
模型上下文協定 (MCP) 允許 AI 模型透過安全的 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=false
當我們設定頒發者 URI 時,Spring Boot 會自動處理 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依賴項:
<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
使用授權碼流程來處理使用者發起的請求。 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. 選出正確的代幣
這裡的全部挑戰在於為每個請求選擇正確的令牌。我們需要一個自訂的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 介面發出請求時,MCP 會使用其授權碼令牌。
7.驗證設定
為了理解該應用程式的工作原理,我們需要研究它產生的結果。在啟動任何應用程式之前,我們必須為 LLM 設定所需的環境變數。設定完成後,我們首先在連接埠 9000 上啟動授權伺服器。這一點很重要,因為所有其他模組都依賴授權伺服器。然後,我們在連接埠 8090 上啟動 MCP 伺服器,然後在連接埠 8080 上啟動 MCP 用戶端。
測試整個流程變得非常簡單。我們需要存取 MCP 用戶端,然後嘗試以下操作:
http://{base_url}:8080/calculate?expression=15+25
客戶端將從授權伺服器取得令牌,使用該令牌呼叫 MCP 伺服器,並傳回計算結果。我們必須確保使用設定檔中指定的憑證登入授權伺服器。
8. 結論
在本教程中,我們探討了 OAuth2 如何透過基於標準令牌的授權機制為 MCP 系統提供強大的安全性。 Spring Security 的 OAuth2 支援能夠以極少的配置實現良好的保護。透過分離授權伺服器、MCP 伺服器和 MCP 用戶端,我們創建了一個架構,每個元件都專注於各自的職責,從而提高了靈活性。
本文支援的程式碼可在 GitHub 上找到。