使用 OAuth2 安全 Spring AI MCP 服務器

Artificial Intelligence,Spring AI,Spring Security
Remote
1
11:11 AM · Nov 30 ,2025

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

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

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

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

首先,我們添加 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 資源服務器 依賴項:

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

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

েন্স>

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 類:

在這裏,我們允許所有授權請求訪問 /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 端點發送請求:

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.