知識庫 / Spring / Spring AI RSS 訂閱

使用OAuth2安全Spring AI MCP服務器

Artificial Intelligence,Spring AI,Spring Security
HongKong
9
10:43 AM · Dec 06 ,2025

MCP(模型上下文協議)是由 Anthropic 引入的開放標準,旨在讓 AI 模型以結構化的方式與外部工具、數據源和服務進行交互。一個 MCP 服務器是一個輕量級的後端應用程序,它通過 MCP 接口暴露特定的能力,例如訪問文件、查詢數據庫或調用 API。

為了使 MCP 服務器具備生產級能力,我們可能會考慮將其分離為獨立的應用程序。這有助於我們獨立地對其進行擴展和維護。然而,由於這些服務器可能會處理敏感任務,因此我們需要保護其端點並限制對受信任客户端的訪問。

這時 OAuth2 登場了。OAuth2 是一種成熟的、基於令牌的、安全 API 訪問委託協議。而不是直接管理用户憑據,我們的 MCP 服務器信任由中心授權服務器頒發的經過驗證的訪問令牌。我們可以使用 OAuth2 來根據範圍和角色授予或限制客户端應用程序對特定 MCP 能力的訪問權限。

在本教程中,我們將學習如何使用 OAuth2 在 Spring AI 應用程序中保護 MCP 服務器。

2. 依賴項

首先,我們需要添加 Spring AI MCP 服務器 依賴項,該依賴項將提供 HTTP 和 SSE 傳輸以及核心 MCP 支持:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>

現在,讓我們添加 OAuth 授權服務器 依賴項。我們將使用它來頒發 OAuth2 訪問令牌:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
    <version>3.3.3</version>
</dependency>

最後,讓我們添加 Spring Spring Security 的資源服務器 依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    <version>3.4.2</version>
</dependency>

藉助此依賴項,我們將確保我們的MCP端點拒絕無效或缺失的 Bearer 令牌。

3. 創建股票信息 MCP 服務器

現在,讓我們實現一個簡單的 MCP 服務器。其中,我們將有一個 工具,該工具將返回給定符號的股票價格。

讓我們創建一個 StockInformationHolder 類:

public class StockInformationHolder {
    @Tool(description = "Get stock price for a company symbol")
    public String getStockPrice(@ToolParam String symbol) {
        if ("AAPL".equalsIgnoreCase(symbol)) {
            return "AAPL: $150.00";
        } else if ("GOOGL".equalsIgnoreCase(symbol)) {
            return "GOOGL: $2800.00";
        } else {
            return symbol + ": Data not available";
        }
    }
}

這裏我們有 getStockPrice() 方法,用於返回已知公司的股票價格以及我們不瞭解的符號的默認響應。 該方法通過 @Tool 註解標記,因此將被用於構建工具定義。 此外,我們還通過 @ToolParam 註解標記了 symbol 參數,以確保它在工具定義構建過程中將被考慮。

接下來,讓我們創建一個 McpServerConfiguration 類:

@Configuration
public class McpServerConfiguration {

    @Bean
    public ToolCallbackProvider stockTools() {
        return MethodToolCallbackProvider
          .builder()
          .toolObjects(new StockInformationHolder())
          .build();
    }
}

這裏,我們提供了 ToolCallbackProvider Bean。我們通過附加 StockInformationHolder 類來構建它。 現在,我們已經擁有一個可用的 MCP 服務器,可以啓動我們的應用程序並通過調用 GET /sse 端點打開 SSE 連接。為了向我們的 MCP 服務器發送消息,讓我們使用 POST /mcp/message 端點,並使用 JSON 負載體:

{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "tools/call",
  "params": {
    "name": "getStockPrice",
    "arguments": {
      "arg0": "AAPL"
    }
  }
}

我們指定了 “tools/call” 作為 method,表明我們希望調用工具的功能。在 params 對象中,我們發送了參數給指定的 method,包括工具的 name,默認值為標註方法名,以及 arguments 映射。

4. 添加安全配置

現在,讓我們為我們的 MCP 服務器添加安全配置。首先,我們將使用 application.yml 文件配置授權服務器

spring:
  security:
    oauth2:
      authorizationserver:
        client:
          oidc-client:
            registration:
              client-id: mcp-client
              client-secret: "{noop}secret"
              client-authentication-methods: client_secret_basic
              authorization-grant-types: client_credentials

我們已經指定了請求令牌的客户端應用程序的唯一標識符。對於共享密鑰,我們使用了 {noop}secret,僅適用於演示目的。 {noop} 前綴告訴 Spring 不對密鑰進行哈希,使其在測試場景中很有用。

接下來,讓我們創建 McpServerSecurityConfiguration 類:

@Configuration
@EnableWebSecurity
public class McpServerSecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
          .authorizeHttpRequests(auth -> auth
            .requestMatchers("/mcp/**").authenticated()
            .requestMatchers("/sse").authenticated()
            .anyRequest().permitAll())
          .with(OAuth2AuthorizationServerConfigurer.authorizationServer(), Customizer.withDefaults())
          .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
          .csrf(CsrfConfigurer::disable)
          .cors(Customizer.withDefaults())
          .build();
    }
}

在此,我們允許所有經過授權的請求到達 /mcp/sse 端點。所有其他端點將保持開放。這種方法簡化了對身份驗證端點的訪問。然而,在實際應用中,我們會更仔細地限制訪問。

我們使用 authorizationServer()oauth2ResourceServer() 方法來配置應用程序。 此配置表明應用程序提供訪問令牌端點,並且它還充當資源服務器,使用 JWT 令牌驗證傳入的請求。

5. 測試安全型 MCP 服務器

現在,我們需要測試我們的安全型 MCP 服務器。讓我們創建一個名為 McpServerOAuth2LiveTest 的類:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class McpServerOAuth2LiveTest {

    private static final Logger log = LoggerFactory.getLogger(McpServerOAuth2LiveTest.class);

    @LocalServerPort
    private int port;

    private WebClient webClient;

    @BeforeEach
    void setup() {
        webClient = WebClient.create("http://localhost:" + port);
    }
}

我們首先以隨機端口啓動應用程序並初始化 WebClient。 接下來,我們調用 /sse 端點以打開服務器端事件連接:

Flux<String> eventStream = webClient.get()
  .uri("/sse")
  .header("Authorization", obtainAccessToken())
  .accept(MediaType.TEXT_EVENT_STREAM)
  .retrieve()
  .bodyToFlux(String.class);

eventStream.subscribe(
    data -> {
        log.info("Response received: {}", data);
        if (!isRequestMessage(data)) {
            assertThat(data).containsSequence("AAPL", "$150");
        }
    },
    error -> log.error("Stream error: {}", error.getMessage()),
    () -> log.info("Stream completed")
);

我們已經確認響應消息包含預期數據。接下來,讓我們向 mcp/message</em/> 終點發送一個請求:

Flux<String> sendMessage = webClient.post()
  .uri("/mcp/message")
  .header("Authorization", obtainAccessToken())
  .contentType(MediaType.APPLICATION_JSON)
  .accept(MediaType.TEXT_EVENT_STREAM)
  .bodyValue("""
     {
         "jsonrpc": "2.0",
         "id": "1",
         "method": "tools/call",
         "params": {
             "name": "getStockPrice",
             "arguments": {
                 "arg0": "AAPL"
             }
         }
     }
     """)
  .retrieve()
  .bodyToFlux(String.class);

我們向系統發送一個請求,以獲取 AAPL 的股票價格。這兩個請求都包含 Authorization 頭部。現在,讓我們來實現獲取訪問令牌的方法:

public String obtainAccessToken() {
    String clientId = "mcp-client";
    String clientSecret = "secret";
    String basicToken = Base64.getEncoder()
      .encodeToString((clientId + ":" + clientSecret).getBytes(StandardCharsets.UTF_8));

    return "Bearer " + webClient.post()
      .uri("/oauth2/token")
      .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
      .header(HttpHeaders.AUTHORIZATION, "Basic " + basicToken)
      .body(BodyInserters.fromFormData("grant_type", "client_credentials"))
      .retrieve()
      .bodyToMono(JsonNode.class)
      .map(node -> node.get("access_token").asText())
      .block(Duration.ofSeconds(5));
}

執行完成後,我們可以看到響應數據已成功接收。這證實我們已通過了安全過濾器。

6. 結論

在本教程中,我們已使用 OAuth2 在 Spring AI 應用程序中安全地配置了 MCP 服務器。為了保護關鍵的 MCP 端點,OAuth2 通過 Spring Boot 進行了無縫集成。此外,該配置具有靈活性,並且可以進一步擴展。例如,我們可以引入基於角色和範圍的訪問控制,以限制特定工具或操作對某些客户端的訪問。

在生產環境中,我們可能會與功能齊全的身份提供程序(如 Keycloak 或 Okta)集成。此外,我們還可以通過添加自定義聲明或範圍來增強令牌,從而控制對 MCP 平台中單個工具的訪問權限。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.