MCP

MCP 如何实现认证鉴权

在上一节课,我们解决了安全传输的问题。然而在实际落地中,如何防止 MCP Server 被滥用同样至关重要。 MCP 本质上允许客户端调用各种工具,这些工具可能访问数据库或内部系统 API。一旦缺乏认证和访问控制,非授权客户端就可能…

TL;DR

在上一节课,我们解决了安全传输的问题。然而在实际落地中,如何防止 MCP Server 被滥用同样至关重要。 MCP 本质上允许客户端调用各种工具,这些工具可能访问数据库或内部系统 API。一旦缺乏认证和访问控制,非授权客户端就可能…

在上一节课,我们解决了安全传输的问题。然而在实际落地中,如何防止 MCP Server 被滥用同样至关重要。 MCP 本质上允许客户端调用各种工具,这些工具可能访问数据库或内部系统 API。一旦缺乏认证和访问控制,非授权客户端就可能冒充合法用户,通过 MCP 调用敏感功能,从而导致数据泄露、系统滥用甚至破坏性操作。 因此,一个真正可用于生产环境的 MCP Server,至少需要解决两个安全问题:传输安全和访问控制。 - 传输安全:通过 HTTPS 确保数据在传输过程中不被窃取或篡改。 - 访问控制:通过认证和鉴权机制(如请求头携带 Bearer Token),防止工具被任意调用。 只有同时保障了这两点,MCP 服务才能具备生产环境下的安全能力。 经过前面的学习,我们已经知道了,MCP Server 分为本地服务和远端服务,本地服务就是 Stdio,远端服务就是 SSE 和 Streamable,这两类服务的认证方式也是有所区别。

本地服务

我们可以去MCP Server的市场看一下,随便找几个公开的MCP Server,我们都能看出来,他们是通过设置env的环境变量来控制权限的。 这种认证方式本质上就是在本地环境中设置一个环境变量 KEY 来区分访问权限,这对于个人使用的智能体环境是可行的,因为使用者只有你自己。但在实际企业场景中,MCP Server 通常不是单人使用的。 举个例子:如果一个产品提供了一个专门查询数据的 MCP Server,不同用户访问的数据范围可能不同,就必须通过传入 token 来区分每个用户的数据权限。而如果 MCP Server 是 stdio 类型,本地环境变量 KEY 会被覆盖,无法实现按用户区分的数据权限控制,因此不适合企业级多用户场景。 Stdio这种方式的认证只需要在初始化的时候传入相应的token即可,比如我用 tavily(一种搜索引擎mcp server)为例:

private McpSyncClient initializeTavilyMcp() {
    Map<String, String> env = new HashMap<>();
    // 填写你自己的 API_KEY,去官网申请
    env.put("TAVILY_API_KEY", "tvly-dev-xxxxxxxxxxxxx");
    // 判断是windows环境还是linux环境
    String osName = System.getProperty("os.name").toLowerCase();
    ServerParameters tavily;
    // 设置参数和环境变量(存放认证key)
    if (osName.contains("win")) {
        tavily = ServerParameters.builder("cmd")
        .args("/c", "npx", "-y", "tavily-mcp")
        .env(env)
        .build();
    } else {
        tavily = ServerParameters.builder("npx")
        .args("-y", "tavily-mcp")
        .env(env)
        .build();
    }
    StdioClientTransport tavTransport = new StdioClientTransport(tavily, McpJsonMapper.createDefault());
    McpSyncClient tavilyMcp = McpClient.sync(tavTransport)
    .loggingConsumer(logingMessage -> {
        log.info("WANGZHEN.......TAVILY SEARCH MCP LOGGING: [" + logingMessage.level() + "] " + logingMessage.data());
    })
    .clientInfo(new McpSchema.Implementation("SEARCH ENGINE", "v1.0"))
    .requestTimeout(Duration.ofSeconds(10)).build();
    tavilyMcp.initialize();
    return tavilyMcp;
}

远端服务

对于远端服务(SSE 与 Streamable),尽管两者的交互模式不同,但认证机制是一致的:客户端通过在请求头中加入 token 进行身份验证。 在服务端,MCP Server 也可以沿用传统 Web 服务的认证方式,通过拦截器对请求头中的 token 进行校验即可。拦截器的实现方式与常规 Web 应用完全相同,例如基于 OAuth、Filter、Interceptor等,这里为了演示方便,选择加一个拦截器来实现此效果。 我们基于上一节课的 HTTPS 的MCP Server来加以改造,新增 AuthInterceptor 拦截器,用于校验token,这边演示效果,直接写死即可。

@Component
public class AuthInterceptor implements HandlerInterceptor {

    private static final String FIXED_TOKEN = "abc123456789";
    private static final String AUTH_HEADER = "Authorization";
    private static final String PREFIX = "Bearer ";

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        String header = request.getHeader(AUTH_HEADER);

        // 判断 header 是否存在
        if (header == null || !header.startsWith(PREFIX)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Missing Authorization header");
            return false;
        }

        // 提取 token
        String token = header.substring(PREFIX.length());

        // 校验 token
        if (!FIXED_TOKEN.equals(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Invalid token");
            return false;
        }

        // 校验成功
        return true;
    }
}

注册拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    public WebConfig(AuthInterceptor authInterceptor) {
        this.authInterceptor = authInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/health");
    }
}

启动项目,访问SSE端点,访问报错,认证不通过,这说明我们的拦截器已经生效了: 接下来,就是要看 MCP Client 如何传递这个token。使用之前的代码,跑一下试试: 发现已经报错401了,然后我们对初始化方法加以改造:

public static void createInsecureHttpsClient(String baseUrl, String endpoint) {
    try {
        // 1. 创建一个信任所有证书的 TrustManager
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }

                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    }

                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    }
                }
        };

        // 2. 初始化 SSL 上下文,绕过校验
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new SecureRandom());

        // 3. 创建并配置 SSLParameters 以禁用主机名验证
        SSLParameters sslParameters = new SSLParameters();
        sslParameters.setEndpointIdentificationAlgorithm(null);

        HttpClient.Builder httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(30))
                .sslContext(sslContext)
                .sslParameters(sslParameters);

        // 4. 设置请求头
        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl))
                .header("Authorization", "Bearer abc123456789");

        HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder(baseUrl).sseEndpoint(endpoint)
                .clientBuilder(httpClient)
                .requestBuilder(requestBuilder)
                .build();
        McpSyncClient mcp = McpClient.sync(transport).build();
        mcp.initialize();
    } catch (Exception e) {
        throw new RuntimeException("创建 Insecure MCP Client 失败", e);
    }
}

重点在31行,设置请求头,通过构造 requestBuilder 在其 header 中设置 Authorization 即可。

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。