SpringAI(GA):MCP Server 服务鉴权(过滤器版)

SpringAI中MCP服务过滤器鉴权

原文链接:SpringAI(GA):MCP Server 服务鉴权(过滤器版)

[!TIP]
以前写过一篇基于工具粒度的鉴权,改动源码挺大的 MCP 服务鉴权—工具粒度。但有些场景可能不需要那么细,控制在 MCP Server 层面即可,这时可以利用过滤器机制,直接捕获 MCP Client 传递过来的 request 请求,进行校验

实战代码可见:https://github.com/GTyingzi/spring-ai-tutorial 下的 mcp 目录下的 mcp-web-auth-server、mcp-auth-client 模块

过滤器机制

Filter 的特性使得 Filter 可以对请求响应进行包装,修改请求头、请求体、响应头、响应体。由于请求先到达 Filter,Filter 还可以做一些全局性的工作。这里用其做请求头鉴权

pom

<dependencies>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
    </dependency>
</dependencies>

application.yml

server:
  port: 19000

spring:
  application:
    name: mcp-auth-mcp-server
  ai:
    mcp:
      server:
        name: mcp-auth-mcp-server
        version: 1.0.0
        type: ASYNC  # Recommended for reactive applications
        instructions: "This reactive server provides time information tools and resources"
        sse-message-endpoint: /mcp/messages
        capabilities:
          tool: true
          resource: true
          prompt: true
          completion: true
# 调试日志
logging:
  level:
    io:
      modelcontextprotocol:
        client: DEBUG
        spec: DEBUG
        server: DEBUG

filter

package com.spring.ai.tutorial.mcp.server.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**
 * @author yingzi
 * @since 2025/9/17
 */
@Component
public class McpServerFilter implements WebFilter {

    private static final String TOKENHEADER = "token-yingzi-1";
    private static final String TOKENVALUE = "yingzi";

    private static final Logger logger = LoggerFactory.getLogger(McpServerFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 获取请求头中的token值
        String token = exchange.getRequest().getHeaders().getFirst(TOKENHEADER);

        // 检查token是否存在且值正确
        if (TOKENVALUE.equals(token)) {
            logger.info("preHandle: 请求的URL: {}", exchange.getRequest().getURI());
            logger.info("preHandle: 请求的TOKEN: {}", token);
            // token验证通过,继续处理请求
            return chain.filter(exchange);
        } else {
            // token验证失败,返回401未授权错误
            logger.warn("Token验证失败: 请求的URL: {}, 提供的TOKEN: {}", exchange.getRequest().getURI(), token);
            exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
}

实现 WebFilter 接口类,设定存在请求头“token-yingzi-1”,且对应值为“yingzi”才放行

tool

package com.spring.ai.tutorial.mcp.server.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author yingzi
 * @date 2025/5/28 08:55
 */

@Service
public class TimeService {

    private static final Logger logger = LoggerFactory.getLogger(TimeService.class);

    @Tool(description = "Get the time of a specified city.")
    public String  getCityTimeMethod(@ToolParam(description = "Time zone id, such as Asia/Shanghai") String timeZoneId) {
        logger.info("The current time zone is {}", timeZoneId);
        return String.format("The current time zone is %s and the current time is " + "%s", timeZoneId,
                getTimeByZoneId(timeZoneId));
    }

    private String getTimeByZoneId(String zoneId) {

        // Get the time zone using ZoneId
        ZoneId zid = ZoneId.of(zoneId);

        // Get the current time in this time zone
        ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);

        // Defining a formatter
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");

        // Format ZonedDateTime as a string
        String formattedDateTime = zonedDateTime.format(formatter);

        return formattedDateTime;
    }
}

提供一个时间服务

启动类

package com.spring.ai.tutorial.mcp.server;

import com.spring.ai.tutorial.mcp.server.service.TimeService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

/**
 * @author yingzi
 * @since 2025/9/17
 */
@SpringBootApplication
public class AuthWebServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthWebServerApplication.class, args);
    }

    @Bean
    public ToolCallbackProvider timeTools(TimeService timeService) {
        return MethodToolCallbackProvider.builder().toolObjects(timeService).build();
    }
}

启动类,对外提供时间服务的 mcp server

效果

  • 首先启动本模块,spring-ai-tutorial 项目的 mcp/server/mcp-auth-web-server 服务
  • 再启动带有请求头的 MCP Client 模块,及 spring-ai-tutorial 项目的 mcp/clien/mcp-auth-client 服务

MCP Client 侧传递请求头及其对应的值

这里看到 MCP Server 侧直接 token 验证失败了


对应的 MCP Client 侧报 401 授权失败

现在让我们把 MCP Server 侧的 TOKENVALUE 改为“yingzi-1”,会发现 MCP Server 验证通过,且 MCP Client 侧正常访问

往期资料

Spring AI + Spring Ai Aliabba系统化学习资料

本教程将采用2025年5月20日正式的GA版,给出如下内容

  1. 核心功能模块的快速上手教程
  2. 核心功能模块的源码级解读
  3. Spring ai alibaba增强的快速上手教程 + 源码级解读

版本:

  • JDK21
  • SpringBoot3.4.5
  • SpringAI 1.0.1
  • SpringAI Alibaba 1.0.0.3+

免费渠道:

  1. 为Spring Ai Alibaba开源社区解决解决有效的issue or 提供有价值的PR,可免费获取上述教程
  2. 往届微信推文

收费服务:收费69.9元

  1. 飞书在线云文档
  2. Spring AI会员群教程代码答疑
  3. 若Spring AI、Spring AI Alibaba教程内容无法满足业务诉求,可定制提供解决方案,带价私聊

学习交流圈

你好,我是影子,曾先后在🐻、新能源、老铁就职,兼任Spring AI Alibaba开源社区的Committer。目前新建了一个交流群,一个人走得快,一群人走得远,另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取

【源码免费下载链接】:https://renmaiwang.cn/s/trd05 Quartus II是一款由Altera公司开发的FPGA(Field-Programmable Gate Array)设计软件,主要用于VHDL和Verilog HDL等硬件描述语言的设计、仿真、综合、配置和编程。对于初学者,掌握Quartus II的基本操作是进入FPGA设计领域的第一步。在Quartus II的使用过程中,首先需要进行的是软件的安装和启动。安装完成后,打开Quartus II,会显示欢迎界面,点击"OK"继续。接下来是创建一个新的工程,这是设计流程的起点。在创建工程时,确保工程文件名与顶层实体名一致,这是良好工程组织习惯的一部分,有助于后期的管理和调试。选择原件(Components)是设计过程中的关键步骤。Quartus II提供了丰富的元件库,包括各种逻辑门、触发器、计数器、D/A转换器等。在本例中,我们选择了四分频触发器。选择原件时,需要根据设计需求和FPGA芯片的特性来决定,不熟悉的原件可以通过查询文档或在线资源来了解其功能和用法。新建VHDL文件是实现逻辑设计的地方。在"File"菜单中选择"New",然后选择VHDL源文件,输入文件名并点击"OK"。接着,就可以在文本编辑器中编写VHDL代码了。VHDL是一种强大的硬件描述语言,用于描述数字系统的结构和行为。四分频触发器的VHDL代码通常会包含一个时钟信号的分频逻辑,例如通过计数器实现。完成代码编写后,需要进行编译。在Quartus II中,编译是通过点击工具栏上的"Start Compilation"按钮或者在"Project"菜单中选择"Compile Project"来实现的。编译过程会检查语法错误、逻辑错误,并生成相应的逻辑结构。编译成功后,可以进行仿真以验证设计的功能。在工具栏上点击仿真图标,或者在"
### 如何使用 Spring AI 实现 MCP Server 要实现基于 Spring AIMCP (Model Context Protocol) 服务器功能,可以按照以下方法和技术细节操作: #### 1. 添加依赖项 为了构建 MCP Server,在项目的 `pom.xml` 文件中需引入特定的 Maven 依赖项。以下是用于创建标准 MCP Server 所需的核心依赖[^3]: ```xml <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server</artifactId> </dependency> ``` 此依赖包含了支持通过 STDIO 和 SSE 方式通信所需的功能模块。 --- #### 2. 创建 MCP Server 项目结构 在初始化项目时,建议遵循典型的 Spring Boot 项目布局。确保配置文件和核心逻辑分离清晰。具体步骤如下: ##### a. 配置 application.properties 或 application.yml 定义基础属性以启用 MCP 协议的支持。例如: ```properties server.port=8080 spring.ai.mcp.server.enabled=true ``` 如果需要自定义协议参数或其他设置,可以在该文件中进一步调整。 ##### b. 主类声明 在主应用程序入口处标注必要的注解以便加载组件和服务提供者。示例代码如下: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } } ``` --- #### 3. 编写核心业务逻辑 MCP Server 的主要职责是处理来自客户端的消息并返回响应。可以通过继承或实现框架预设接口完成定制化需求。 ##### a. 自定义处理器 编写一个消息处理器来解析传入的数据包并与模型上下文交互。下面是一个简单的例子: ```java import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @Component public class CustomMessageHandler { public Mono<String> handleMessage(String input) { // 对输入进行处理并生成输出 String response = "Processed: " + input; return Mono.just(response); } } ``` 此处利用了 Reactor 提供的异步编程能力,适合高并发场景下的快速响应设计。 ##### b. 整合到控制器层 将上述处理器绑定至 HTTP 请求路径或者流式事件监听机制下。样例展示如下: ```java import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class McpController { private final CustomMessageHandler messageHandler; public McpController(CustomMessageHandler messageHandler) { this.messageHandler = messageHandler; } @PostMapping("/mcp/process") public Mono<String> process(@RequestBody String data) { return messageHandler.handleMessage(data); } } ``` 这样即可对外暴露 RESTful API 接口供外部调用。 --- #### 4. 启动与测试 当所有组件部署完毕后,运行应用程序并通过 Postman 或其他工具发送 POST 请求验证其工作状态。假设 URL 路径为 `/mcp/process`,则请求体可能形如 JSON 字符串形式传递给后台服务端点。 --- ### 技术优势总结 借助于 Spring AI 中集成好的 MCP 支持库,开发者能够迅速搭建起具备标准化通讯模式的人工智能服务平台。这种方案不仅简化了跨平台协作流程,还极大提升了系统的可维护性和扩展性[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值