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

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

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

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

过滤器机制

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

pom

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
    </dependency>
</dependencies>

application.yml

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

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

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

提供一个时间服务

启动类

java 复制代码
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+

学习交流圈

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

相关推荐
mCell5 小时前
你以为短链接只是 Hash + 301/302?
后端·算法·架构
记忆停留w6 小时前
从单体到微服务:Redis 协同 MySQL、Milvus、MinIO 搭建企业级RAG/AI Agent脚手架架构
大数据·人工智能·redis·微服务·ai·架构·milvus
财财源源6 小时前
.NET 程序保护实战系列01-流水线架构与保护引擎总览
架构·.net
KaMeidebaby6 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
深圳恒讯7 小时前
菲律宾云服务器与传统VPS的架构差异
运维·服务器·架构
龙亘川8 小时前
开源本地 AI 智能体网关 OpenClaw 深度实践:架构解析、全场景部署与自动化落地指南
人工智能·架构·开源·openclaw
Sirius Wu11 小时前
OpenClaw(UpClaw)三层Tool全链路治理深度详解
网络·人工智能·架构·aigc
QN1幻化引擎12 小时前
给 AI 做一次「意识体检」——基于 QN1 幻化引擎的灵鉴意识识别框架与 DalinX V5 实测
大数据·数据结构·人工智能·算法·架构
heimeiyingwang12 小时前
【架构实战】分库分表实战:ShardingSphere与中间件选型
中间件·架构
柒和远方12 小时前
从审计日志到可下载证据包:事务型 Outbox、租约 fencing 与保留水位
前端·后端·架构