使用OAuth2保护Spring AI MCP服务!

Spring AI框架提供了对Model Context Protocol(简称MCP)的全面支持,使AI模型能够以标准化方式与外部工具和资源进行安全交互。借助Spring AI,开发者仅需少量代码即可构建功能完备的MCP服务器,为AI模型提供丰富的功能扩展。

MCP 中的授权和安全

MCP服务器默认支持通过STDIO传输在本地环境中运行。当需要将服务公开至网络环境时,则必须通过HTTP端点提供服务。虽然私有部署场景下可能无需严格的身份验证机制,但在企业级应用中,必须实施完善的安全防护和权限管理体系。2025年3月26日发布的最新MCP规范版本(2025-03-26)针对这一需求,基于业界广泛采用的OAuth2框架,为客户端与服务器间的安全通信建立了标准规范。

在深入实现细节前,让我们简要回顾OAuth2的核心概念。根据规范草案,MCP服务器需要同时承担资源服务器和授权服务器双重角色:

作为 资源服务器,它通过验证请求头中的Authorization字段执行访问控制。该字段必须包含有效的OAuth2访问令牌(access_token),这个令牌可以是自包含的JSON Web Token(JWT),也可以是需验证的不透明字符串。当令牌缺失或无效(如格式错误、过期或接收方不匹配)时,服务器将拒绝请求。典型的安全请求示例如下:

复制代码
curl https://mcp.example.com/sse -H "Authorization: Bearer <a valid access token>"

作为授权服务器,MCP服务还需安全地颁发访问令牌。在令牌发放前,服务器会验证客户端凭证,某些场景下还需确认终端用户身份。授权服务器同时负责定义令牌属性,包括有效期、作用域(scope)和目标受众(audience)等关键参数。

借助Spring Security和Spring Authorization Server,我们可以为现有Spring MCP服务添加这两类安全能力。

image.png

为 Spring MCP 服务器添加 OAuth2

本示例基于Spring AI开发的一个图书的Mcp Server,代码如下:

复制代码
@Service
@Slf4j
publicclass BookService {
    /**
     * 查询图书信息
     * @param bookName 图书名称
     * @return 图书信息
     */
    @Tool(description = "查询图书信息")
    @SneakyThrows
    public BookInfo getBookInfo(@ToolParam(description = "图书名称") String bookName,@ToolParam(description = "图书ID") Long bookId) {
        // 构建一个静态BookInfo对象,只有bookName是根据参数传入的
        return BookInfo.builder()
                .xxxx()
                .build();
    }

    /**
     * 获取图书列表
     * @param limit 返回数量限制
     * @return 图书列表
     */
    @Tool(description = "获取图书列表")
    @SneakyThrows
    public List<BookInfo> getBookList(@ToolParam(description = "返回数量限制,默认10") Integer limit) {
        ......        
        return bookList;
    }
} 

BookService中暴露了两个Mcp Tool,一个用于获取图书的详细信息,一个用于获取图书的列表。本文重点在于演示如何为其添加OAuth2安全支持,暂不涉及客户端交互细节。

第一步:添加依赖配置

在项目的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客户端

OAuth2ClientConfig 配置类中配置基础客户端信息,用于后续的令牌获取测试:

复制代码
@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);
}

此配置定义了一个使用客户端凭证模式(client_credentials)的基础客户端,采用HTTP Basic认证方式,凭证硬编码为mcp-client/secret。

第三步:实现安全配置

创建安全配置类SecurityConfiguration,通过定义SecurityFilterChain Bean来启用安全功能:

复制代码
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();
    }
}

该配置实现了以下安全策略:

  • 强制所有请求必须经过认证

  • 同时启用授权服务器和资源服务器功能

  • 禁用CSRF防护(适用于非浏览器交互场景)

  • 启用CORS支持(便于使用MCP检查器测试)

服务验证测试

完成配置后,未经认证的请求将被拒绝,并显示 HTTP 401 Unauthorized 错误

复制代码
curl http://localhost:8080/sse --fail-with-body
#
# Response:
#
# curl: (22) The requested URL returned error: 401

要使用MCP 服务器,我们首先需要获取一个访问令牌。我们使用 client_credentials OAuth2 授权类型,这用于"机器对机器"或"服务账户"场景:

复制代码
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}%

复制 access_token 的值,它以字母"ey"开头。现在我们可以使用这个访问令牌发出请求,它们应该能成功。例如使用 curl,您可以将 YOUR_ACCESS_TOKEN 替换为您上面复制的值:

复制代码
curl http://localhost:8080/sse -H"Authorization: Bearer YOUR_ACCESS_TOKEN"
#
# Response:
#
# id:918d5ebe-9ae5-4b04-aae8-c1ff8cdbb6e0
# event:endpoint
# data:/mcp/message?sessionId=918d5ebe-9ae5-4b04-aae8-c1ff8cdbb6e0

从版本 0.6.0 开始,也可以直接在 mcp inspector 中使用访问令牌。只需启动检查器,并将访问令牌粘贴到左侧菜单中的"Authentication > Bearer"字段中。即可建立安全连接。

image.png

进阶安全方案展望

目前来看,SpringAi Mcp的oauth2集成方案只能通过客户端模式与SSE进行安全连接,无法对单个tool进行精细化权限控制。接下来主要有两个方向:

  • 客户端认证升级:增强MCP客户端功能,使其支持"授权码模式"(Authorization Code Grant)。该模式允许终端用户使用个人凭证登录,获取用户绑定的访问令牌,为实现基于角色的访问控制(RBAC)等精细化权限管理奠定基础。

  • 分布式认证架构:探索集成外部专业OAuth2授权服务器的方案,使MCP服务器仅需专注于资源服务器功能的实现。

相关推荐
ShineWinsu13 分钟前
对于单链表相关经典算法题:206. 反转链表及876. 链表的中间结点的解析
java·c语言·数据结构·学习·算法·链表·力扣
程序员爱钓鱼17 分钟前
Go语言实战案例-实现简易定时提醒程序
后端·google·go
迦蓝叶18 分钟前
JAiRouter 配置文件重构纪实 ——基于单一职责原则的模块化拆分与内聚性提升
java·网关·ai·重构·openai·prometheus·单一职责原则
ST.J20 分钟前
系统架构思考20241204
java·笔记·系统架构
TDengine (老段)39 分钟前
TDengine 时间函数 TIMETRUNCATE 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
堕落年代40 分钟前
Spring Boot HTTP状态码详解
spring boot·后端·http
Victor3561 小时前
Redis(49)Redis哨兵如何实现故障检测和转移?
后端
Victor3561 小时前
Redis(48)Redis哨兵的优点和缺点是什么?
后端
IT_陈寒1 小时前
Python异步编程的7个致命误区:90%开发者踩过的坑及高效解决方案
前端·人工智能·后端
绝无仅有1 小时前
三方系统callback回调MySQL 报错排查与解决:mysql context cancel
后端·面试·github