Spring AI Alibaba MCP 协议的全链路安全与动态鉴权

上一篇文章中,我们成功搭建了 MCP Server 并实现了天气查询功能。然而,在真实的生产环境中,我们的 AI 应用不可能是"裸奔"的。我们需要解决两个核心安全问题:

  1. 通道安全:MCP Client 如何向 Server 证明自己的身份?(Token 传递与校验)
  2. 应用安全:不同角色的用户(如 Admin 与 Guest)看到的工具应该是一样的吗?(动态工具鉴权)

本文将通过 Header 透传 Token 实现服务端鉴权,并结合 Spring Security 实现千人千面的动态工具授权。

第一关:MCP Client 与 Server 之间的 Token 透传

MCP 协议通常基于 HTTP(SSE/STDIO) 传输。为了保证 Server 端接口不被非法调用,我们需要在 Client 发起请求时,自动注入认证 Token。

1. Client 端:自定义 Header 注入器

我们需要实现 McpSyncHttpClientRequestCustomizer 接口,拦截所有的 MCP 请求,并在 Header 中注入 Token。

java 复制代码
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
import io.modelcontextprotocol.common.McpTransportContext;

import java.net.URI;
import java.net.http.HttpRequest;
import java.util.Map;

public class HeaderSyncHttpRequestCustomizer implements McpSyncHttpClientRequestCustomizer {

    private final Map<String, String> headers;

    public HeaderSyncHttpRequestCustomizer(Map<String, String> headers) {
        this.headers = headers;
    }

    @Override
    public void customize(HttpRequest.Builder builder, String method, URI endpoint, String body, McpTransportContext context) {
        headers.forEach(builder::header);
    }
}

2. 配置 Client 注入 Bean

在配置类中,定义我们需要透传的 Key-Value 对(例如 token-1: yingzi-1)。

java 复制代码
import com.agent.cloud.client.HeaderSyncHttpRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class HttpClientConfig {

    @Bean
    public McpSyncHttpClientRequestCustomizer mcpAsyncHttpClientRequestCustomizer() {
        Map<String, String> headers = new HashMap<>();
        headers.put("token-1", "yingzi-1");
        headers.put("token-2", "yingzi-2");

        return new HeaderSyncHttpRequestCustomizer(headers);
    }
}

第二关:MCP Server 端的 Token 校验

Server 端接收到请求后,需要验证 Header 中的 Token 是否合法。这里我们使用标准的 Servlet Filter 来实现。

1. 编写鉴权 Filter

java 复制代码
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class McpServerFilter implements Filter {

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

    private static final Logger logger = LoggerFactory.getLogger(McpServerFilter.class);
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 获取请求头中的token值
        String token = httpRequest.getHeader(TOKENHEADER);

        // 打印所有请求头信息
        java.util.Enumeration<String> headerNames = httpRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            logger.info("Header {}: {}", headerName, httpRequest.getHeader(headerName));
        }

        // 检查token是否存在且值正确
        if (TOKENVALUE.equals(token)) {
            logger.info("preHandle: 验证通过");
            logger.info("preHandle: 请求的URL: {}", httpRequest.getRequestURL());
            logger.info("preHandle: 请求的TOKEN: {}", token);
            // token验证通过,继续处理请求
            chain.doFilter(request, response);
        } else {
            // token验证失败,返回401未授权错误
            logger.warn("Token验证失败: 请求的URL: {}, 提供的TOKEN: {}",
                    httpRequest.getRequestURL(), token);
            logger.warn("要求的token为:{}", TOKENVALUE);
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}

2. 注册 Filter 并配置路径

由于 MCP 可能涉及 SSE(Server-Sent Events)长连接,必须开启异步支持。

java 复制代码
import com.agent.cloud.filter.McpServerFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean<McpServerFilter> mcpServerFilterRegistration() {
        FilterRegistrationBean<McpServerFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new McpServerFilter());
        // 设置异步支持
        registration.setAsyncSupported(true);
        // 设置最高优先级
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }
}

基于角色的动态工具鉴权

仅仅校验 Token 只是第一步。真正的"妙用无穷"在于动态鉴权。在上一篇博客中,我们注册了"天气查询"工具。现在,我们可以根据用户角色,决定是否让 AI 看到这个工具。

场景设想

  • Admin 角色:可以看到所有工具(天气、删除数据、系统配置)。
  • Guest 角色:只能看到基础工具(天气)。

实现思路

在构建 ReactAgent 时,不要硬编码工具列表,而是根据当前登录用户的角色动态筛选。

推荐在数据库或配置文件中维护 tool_namerole_list 的映射,在 Client 端构建 ReactAgent 之前,先查询这个映射表,根据当前用户的角色,筛选出他能看到的工具列表。

为了防止 Client 端被绕过,Server 端在执行工具时,最好再校验一次当前 Token 是否真的有权限。

总结

通过本文的实战,我们完成了 Spring AI Alibaba MCP 的安全加固:

  1. 通道层 :利用 McpSyncHttpClientRequestCustomizer 实现了 Client 到 Server 的 Token 自动透传。
  2. 服务层:利用 Filter 实现了 MCP 接口的准入控制,拒绝非法调用。
  3. 应用层:引申出的动态鉴权机制,让 AI 能够根据用户身份"千人千面",不仅提升了安全性,还让 AI 的交互更加智能和精准。

安全无小事,尤其是在 AI 时代,控制好工具的"钥匙",就是控制好企业的核心数据。

相关推荐
Bruce20489982 小时前
2026 云原生安全:Rust 编写微服务网关与零信任实践
安全·云原生·rust
moton20172 小时前
TLS会话恢复机制深度解析:Session ID、Ticket 与 TLS1.3 PSK架构
数据库·网络协议·安全·架构·ssl·物联网架构
sonnet-10292 小时前
堆排序算法
java·c语言·开发语言·数据结构·python·算法·排序算法
Sombra_Olivia2 小时前
Vulhub 中的 apache-cxf CVE-2024-28752
安全·web安全·网络安全·渗透测试·vulhub
我是咸鱼不闲呀2 小时前
力扣Hot100系列24(Java)——[回溯]总结(下)(括号生成,单词搜索,分割回文串)
java·算法·leetcode
升鲜宝供应链及收银系统源代码服务2 小时前
生鲜配送供应链管理系统源代码之升鲜宝社区团购商城小程序(一)
java·前端·数据库·小程序·notepad++·供应链系统源代码·多门店收银系统
墨香幽梦客2 小时前
大数据环境下的BI架构:Hadoop与Spark的企业级应用整理
java·开发语言
亚信安全官方账号2 小时前
亚信安全终端安全融合“龙虾”,发布TrustOne 安全助理
大数据·人工智能·安全
卓豪终端管理2 小时前
当补丁还在路上,如何打赢零日漏洞的时间战?
网络·安全·web安全