使用OAuth2保護(hù)Spring AI MCP服務(wù)!
Spring AI框架提供了對(duì)Model Context Protocol(簡(jiǎn)稱(chēng)MCP)的全面支持,使AI模型能夠以標(biāo)準(zhǔn)化方式與外部工具和資源進(jìn)行安全交互。借助Spring AI,開(kāi)發(fā)者僅需少量代碼即可構(gòu)建功能完備的MCP服務(wù)器,為AI模型提供豐富的功能擴(kuò)展。
MCP 中的授權(quán)和安全
MCP服務(wù)器默認(rèn)支持通過(guò)STDIO傳輸在本地環(huán)境中運(yùn)行。當(dāng)需要將服務(wù)公開(kāi)至網(wǎng)絡(luò)環(huán)境時(shí),則必須通過(guò)HTTP端點(diǎn)提供服務(wù)。雖然私有部署場(chǎng)景下可能無(wú)需嚴(yán)格的身份驗(yàn)證機(jī)制,但在企業(yè)級(jí)應(yīng)用中,必須實(shí)施完善的安全防護(hù)和權(quán)限管理體系。2025年3月26日發(fā)布的最新MCP規(guī)范版本(2025-03-26)針對(duì)這一需求,基于業(yè)界廣泛采用的OAuth2框架,為客戶(hù)端與服務(wù)器間的安全通信建立了標(biāo)準(zhǔn)規(guī)范。
在深入實(shí)現(xiàn)細(xì)節(jié)前,讓我們簡(jiǎn)要回顧OAuth2的核心概念。根據(jù)規(guī)范草案,MCP服務(wù)器需要同時(shí)承擔(dān)資源服務(wù)器和授權(quán)服務(wù)器雙重角色:
作為 資源服務(wù)器,它通過(guò)驗(yàn)證請(qǐng)求頭中的Authorization字段執(zhí)行訪(fǎng)問(wèn)控制。該字段必須包含有效的OAuth2訪(fǎng)問(wèn)令牌(access_token),這個(gè)令牌可以是自包含的JSON Web Token(JWT),也可以是需驗(yàn)證的不透明字符串。當(dāng)令牌缺失或無(wú)效(如格式錯(cuò)誤、過(guò)期或接收方不匹配)時(shí),服務(wù)器將拒絕請(qǐng)求。典型的安全請(qǐng)求示例如下:
curl https://mcp.example.com/sse -H "Authorization: Bearer <a valid access token>"作為授權(quán)服務(wù)器,MCP服務(wù)還需安全地頒發(fā)訪(fǎng)問(wèn)令牌。在令牌發(fā)放前,服務(wù)器會(huì)驗(yàn)證客戶(hù)端憑證,某些場(chǎng)景下還需確認(rèn)終端用戶(hù)身份。授權(quán)服務(wù)器同時(shí)負(fù)責(zé)定義令牌屬性,包括有效期、作用域(scope)和目標(biāo)受眾(audience)等關(guān)鍵參數(shù)。
借助Spring Security和Spring Authorization Server,我們可以為現(xiàn)有Spring MCP服務(wù)添加這兩類(lèi)安全能力。
image.png
為 Spring MCP 服務(wù)器添加 OAuth2
本示例基于Spring AI開(kāi)發(fā)的一個(gè)圖書(shū)的Mcp Server,代碼如下:
@Service
@Slf4j
publicclass BookService {
/**
* 查詢(xún)圖書(shū)信息
* @param bookName 圖書(shū)名稱(chēng)
* @return 圖書(shū)信息
*/
@Tool(description = "查詢(xún)圖書(shū)信息")
@SneakyThrows
public BookInfo getBookInfo(@ToolParam(description = "圖書(shū)名稱(chēng)") String bookName,@ToolParam(description = "圖書(shū)ID") Long bookId) {
// 構(gòu)建一個(gè)靜態(tài)BookInfo對(duì)象,只有bookName是根據(jù)參數(shù)傳入的
return BookInfo.builder()
.xxxx()
.build();
}
/**
* 獲取圖書(shū)列表
* @param limit 返回?cái)?shù)量限制
* @return 圖書(shū)列表
*/
@Tool(description = "獲取圖書(shū)列表")
@SneakyThrows
public List<BookInfo> getBookList(@ToolParam(description = "返回?cái)?shù)量限制,默認(rèn)10") Integer limit) {
......
return bookList;
}
}BookService中暴露了兩個(gè)Mcp Tool,一個(gè)用于獲取圖書(shū)的詳細(xì)信息,一個(gè)用于獲取圖書(shū)的列表。本文重點(diǎn)在于演示如何為其添加OAuth2安全支持,暫不涉及客戶(hù)端交互細(xì)節(jié)。
第一步:添加依賴(lài)配置
在項(xiàng)目的pom.xml中引入必要的Spring Boot starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>第二步:配置OAuth2客戶(hù)端
在OAuth2ClientConfig 配置類(lèi)中配置基礎(chǔ)客戶(hù)端信息,用于后續(xù)的令牌獲取測(cè)試:
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient mcpClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("mcp-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofHours(1))
.build())
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(false)
.build())
.scope("LIST")
.build();
returnnew InMemoryRegisteredClientRepository(mcpClient);
}此配置定義了一個(gè)使用客戶(hù)端憑證模式(client_credentials)的基礎(chǔ)客戶(hù)端,采用HTTP Basic認(rèn)證方式,憑證硬編碼為mcp-client/secret。
第三步:實(shí)現(xiàn)安全配置
創(chuàng)建安全配置類(lèi)SecurityConfiguration,通過(guò)定義SecurityFilterChain Bean來(lái)啟用安全功能:
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer.authorizationServer;
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.with(authorizationServer(), Customizer.withDefaults())
.oauth2ResourceServer(resource -> resource.jwt(Customizer.withDefaults()))
.csrf(CsrfConfigurer::disable)
.cors(Customizer.withDefaults())
.build();
}
}該配置實(shí)現(xiàn)了以下安全策略:
- 強(qiáng)制所有請(qǐng)求必須經(jīng)過(guò)認(rèn)證
- 同時(shí)啟用授權(quán)服務(wù)器和資源服務(wù)器功能
- 禁用CSRF防護(hù)(適用于非瀏覽器交互場(chǎng)景)
- 啟用CORS支持(便于使用MCP檢查器測(cè)試)
服務(wù)驗(yàn)證測(cè)試
完成配置后,未經(jīng)認(rèn)證的請(qǐng)求將被拒絕,并顯示 HTTP 401 Unauthorized 錯(cuò)誤
curl http://localhost:8080/sse --fail-with-body
#
# Response:
#
# curl: (22) The requested URL returned error: 401要使用MCP 服務(wù)器,我們首先需要獲取一個(gè)訪(fǎng)問(wèn)令牌。我們使用 client_credentials OAuth2 授權(quán)類(lèi)型,這用于"機(jī)器對(duì)機(jī)器"或"服務(wù)賬戶(hù)"場(chǎng)景:
curl -XPOST http://localhost:8080/oauth2/token --data grant_type=client_credentials --user mcp-client:secret
#
# Response:
#
# {"access_token":"<YOUR-ACCESS-TOKEN>","token_type":"Bearer","expires_in":3599}%復(fù)制 access_token 的值,它以字母"ey"開(kāi)頭?,F(xiàn)在我們可以使用這個(gè)訪(fǎng)問(wèn)令牌發(fā)出請(qǐng)求,它們應(yīng)該能成功。例如使用 curl,您可以將 YOUR_ACCESS_TOKEN 替換為您上面復(fù)制的值:
curl http://localhost:8080/sse -H"Authorization: Bearer YOUR_ACCESS_TOKEN"
#
# Response:
#
# id:918d5ebe-9ae5-4b04-aae8-c1ff8cdbb6e0
# event:endpoint
# data:/mcp/message?sessinotallow=918d5ebe-9ae5-4b04-aae8-c1ff8cdbb6e0從版本 0.6.0 開(kāi)始,也可以直接在 mcp inspector 中使用訪(fǎng)問(wèn)令牌。只需啟動(dòng)檢查器,并將訪(fǎng)問(wèn)令牌粘貼到左側(cè)菜單中的"Authentication > Bearer"字段中。即可建立安全連接。

進(jìn)階安全方案展望
目前來(lái)看,SpringAi Mcp的oauth2集成方案只能通過(guò)客戶(hù)端模式與SSE進(jìn)行安全連接,無(wú)法對(duì)單個(gè)tool進(jìn)行精細(xì)化權(quán)限控制。接下來(lái)主要有兩個(gè)方向:
- 客戶(hù)端認(rèn)證升級(jí):增強(qiáng)MCP客戶(hù)端功能,使其支持"授權(quán)碼模式"(Authorization Code Grant)。該模式允許終端用戶(hù)使用個(gè)人憑證登錄,獲取用戶(hù)綁定的訪(fǎng)問(wèn)令牌,為實(shí)現(xiàn)基于角色的訪(fǎng)問(wèn)控制(RBAC)等精細(xì)化權(quán)限管理奠定基礎(chǔ)。
- 分布式認(rèn)證架構(gòu):探索集成外部專(zhuān)業(yè)OAuth2授權(quán)服務(wù)器的方案,使MCP服務(wù)器僅需專(zhuān)注于資源服務(wù)器功能的實(shí)現(xiàn)。
































