You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用-Djavax.net.debug如何自定义记录mTLS握手详情

针对你使用Java 17、Spring Boot 2.6.8 + WebClient发起mTLS请求的场景,不需要开启全局javax.net.debug调试参数,有完全可插拔、日志内容/格式/级别完全可控的实现方案,不会产生冗余日志。


方案1:基于Reactor Netty SSL监听实现(推荐,和WebClient过滤器体验一致)

Spring Boot 2.6.x版本默认使用Reactor Netty作为WebClient的底层客户端,你可以在构建WebClient时定制HttpClient的SSL逻辑,注册握手完成事件监听器,直接提取需要的所有握手元数据,完全不侵入原有业务逻辑,也不会修改全局JVM SSL配置。

核心实现代码如下:

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import lombok.extern.slf4j.Slf4j;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Arrays;

@Slf4j
public class WebClientMTLSConfig {

    public WebClient buildMTLSWebClient(X509ExtendedKeyManager keyManager, X509ExtendedTrustManager trustManager) {
        // 1. 构建SSL上下文,完全复用你原有的密钥库/信任库配置,不改变原有证书自动选择逻辑
        SslContext sslContext = SslContextBuilder.forClient()
                .keyManager(keyManager)
                .trustManager(trustManager)
                .sslHandshakeTimeout(Duration.ofSeconds(10))
                .build();

        // 2. 配置HttpClient,给每个连接绑定握手事件监听器
        HttpClient httpClient = HttpClient.create()
                .secure(sslSpec -> sslSpec.sslContext(sslContext))
                .doOnConnected(conn -> conn.channel().pipeline().addLast("mtls-handshake-logger", new ChannelInboundHandlerAdapter() {
                    @Override
                    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                        if (evt instanceof SslHandshakeCompletionEvent handshakeEvent) {
                            try {
                                if (handshakeEvent.isSuccess()) {
                                    SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
                                    SSLEngine sslEngine = sslHandler.engine();
                                    SSLSession session = sslEngine.getSession();

                                    // 按需提取所有需要的握手信息
                                    // 提取客户端本次选中发送的证书Subject DN
                                    X509Certificate[] clientCerts = session.getLocalCertificates() != null
                                            ? Arrays.stream(session.getLocalCertificates())
                                            .map(cert -> (X509Certificate) cert)
                                            .toArray(X509Certificate[]::new)
                                            : new X509Certificate[0];
                                    String clientCertSubject = clientCerts.length > 0
                                            ? clientCerts[0].getSubjectX500Principal().getName()
                                            : "未携带客户端证书";

                                    // 提取协商的TLS版本、加密套件
                                    String tlsVersion = session.getProtocol();
                                    String cipherSuite = session.getCipherSuite();

                                    // 按需提取服务端证书信息、对端地址等
                                    X509Certificate[] serverCerts = (X509Certificate[]) session.getPeerCertificates();
                                    String serverCertSubject = serverCerts.length > 0
                                            ? serverCerts[0].getSubjectX500Principal().getName()
                                            : "未知服务端证书";

                                    // 完全自定义日志格式、级别
                                    log.info("mTLS握手完成 | 对端地址:{} | TLS版本:{} | 加密套件:{} | 客户端证书DN:{} | 服务端证书DN:{}",
                                            ctx.channel().remoteAddress(),
                                            tlsVersion,
                                            cipherSuite,
                                            clientCertSubject,
                                            serverCertSubject);
                                } else {
                                    log.error("mTLS握手失败 | 对端地址:{}", ctx.channel().remoteAddress(), handshakeEvent.cause());
                                }
                            } finally {
                                // 握手只执行一次,处理完移除当前handler,避免连接复用时重复触发
                                ctx.pipeline().remove(this);
                            }
                        }
                        super.userEventTriggered(ctx, evt);
                    }
                }));

        // 3. 构建WebClient,原有过滤器、拦截器逻辑完全兼容
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                // 可继续添加你现有的ExchangeFilterFunction等过滤器
                .build();
    }
}

这个方案的优势:

  • 插拔式配置,仅影响当前WebClient实例,不会修改全局JVM SSL行为,不会产生无关日志
  • 完全不干扰原有多证书链自动选择逻辑,只是在握手完成节点读取元数据打日志
  • 性能开销极低,监听器仅在握手阶段触发一次,连接复用后无额外消耗
  • 日志字段、格式、输出级别完全自主控制

方案2:JDK HttpClient底层适配方案(备选)

如果你将WebClient底层切换为JDK 11+原生HttpClient,可以通过代理X509ExtendedKeyManager的方式埋点:

  • 包装你原有的KeyManager实现,重写chooseClientAlias、chooseEngineClientAlias方法,在客户端选中证书链时记录证书Subject DN、别名等信息
  • 给SSLEngine注册HandshakeCompletedListener,在握手完成回调中提取协商的TLS版本、加密套件、会话信息等内容
  • 将包装后的KeyManager、TrustManager注入SSLContext,再绑定到JDK HttpClient,最后传入WebClient构造器即可

这个方案同样是实例级配置,不会产生全局冗余日志,但适配代码比Reactor Netty方案稍繁琐。


不推荐的实现方式

  • 不要使用-Djavax.net.debug=all,handshake全局参数:该配置会打印JVM内所有SSL连接(包括第三方组件发起的HTTPS请求)的调试信息,日志量极大且格式固定无法自定义,生产环境开启很容易打满日志磁盘。
  • 不要通过替换全局Security Provider、修改默认SSLContext的方式做埋点:侵入性太高,容易影响应用内其他组件的SSL逻辑,排查问题成本高。

内容的提问来源于stack exchange,提问作者hallo02

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:09:45