在上一篇文章中,我们成功搭建了 MCP Server 并实现了天气查询功能。然而,在真实的生产环境中,我们的 AI 应用不可能是"裸奔"的。我们需要解决两个核心安全问题:
- 通道安全:MCP Client 如何向 Server 证明自己的身份?(Token 传递与校验)
- 应用安全:不同角色的用户(如 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_name 和 role_list 的映射,在 Client 端构建 ReactAgent 之前,先查询这个映射表,根据当前用户的角色,筛选出他能看到的工具列表。
为了防止 Client 端被绕过,Server 端在执行工具时,最好再校验一次当前 Token 是否真的有权限。
总结
通过本文的实战,我们完成了 Spring AI Alibaba MCP 的安全加固:
- 通道层 :利用
McpSyncHttpClientRequestCustomizer实现了 Client 到 Server 的 Token 自动透传。 - 服务层:利用 Filter 实现了 MCP 接口的准入控制,拒绝非法调用。
- 应用层:引申出的动态鉴权机制,让 AI 能够根据用户身份"千人千面",不仅提升了安全性,还让 AI 的交互更加智能和精准。
安全无小事,尤其是在 AI 时代,控制好工具的"钥匙",就是控制好企业的核心数据。