Spring Boot Rest控制器中如何获取SSLSession内的TLS指纹数据?
实现步骤
1. 自定义SSLEngine包装类,捕获握手事件并存储TLS指纹
创建包装类封装原生SSLEngine,添加握手完成监听器,在握手结束后生成TLS指纹并存入SSLSession属性:
import javax.net.ssl.*; import java.nio.ByteBuffer; public class FingerprintSSLEngine extends SSLEngine { private final SSLEngine delegate; public FingerprintSSLEngine(SSLEngine delegate) { this.delegate = delegate; delegate.addHandshakeCompletedListener(this::onHandshakeCompleted); } private void onHandshakeCompleted(HandshakeCompletedEvent event) { SSLSession session = event.getSession(); try { String tlsFingerprint = generateTlsFingerprint(session); session.putValue("TLS_FINGERPRINT", tlsFingerprint); } catch (Exception e) { // 生产环境建议替换为日志记录 e.printStackTrace(); } } private String generateTlsFingerprint(SSLSession session) { // 示例:基于协议版本+加密套件生成简易指纹,生产环境请用标准JA3/HA2等指纹算法 String protocol = session.getProtocol(); String cipherSuite = session.getCipherSuite(); return String.format("%s_%s", protocol, cipherSuite).hashCode() + ""; } // 重写所有SSLEngine方法,委托给原生实例 @Override public void setUseClientMode(boolean mode) { delegate.setUseClientMode(mode); } @Override public boolean getUseClientMode() { return delegate.getUseClientMode(); } @Override public void setNeedClientAuth(boolean need) { delegate.setNeedClientAuth(need); } @Override public boolean getNeedClientAuth() { return delegate.getNeedClientAuth(); } @Override public void setWantClientAuth(boolean want) { delegate.setWantClientAuth(want); } @Override public boolean getWantClientAuth() { return delegate.getWantClientAuth(); } @Override public void setEnableSessionCreation(boolean flag) { delegate.setEnableSessionCreation(flag); } @Override public boolean getEnableSessionCreation() { return delegate.getEnableSessionCreation(); } @Override public String getPeerHost() { return delegate.getPeerHost(); } @Override public int getPeerPort() { return delegate.getPeerPort(); } @Override public SSLSession getSession() { return delegate.getSession(); } @Override public void beginHandshake() throws SSLException { delegate.beginHandshake(); } @Override public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) throws SSLException { return delegate.wrap(srcs, offset, length, dst); } @Override public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) throws SSLException { return delegate.unwrap(src, dsts, offset, length); } @Override public void closeInbound() throws SSLException { delegate.closeInbound(); } @Override public boolean isInboundDone() { return delegate.isInboundDone(); } @Override public void closeOutbound() { delegate.closeOutbound(); } @Override public boolean isOutboundDone() { return delegate.isOutboundDone(); } @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } @Override public String[] getEnabledCipherSuites() { return delegate.getEnabledCipherSuites(); } @Override public void setEnabledCipherSuites(String[] suites) { delegate.setEnabledCipherSuites(suites); } @Override public String[] getSupportedProtocols() { return delegate.getSupportedProtocols(); } @Override public String[] getEnabledProtocols() { return delegate.getEnabledProtocols(); } @Override public void setEnabledProtocols(String[] protocols) { delegate.setEnabledProtocols(protocols); } @Override public SSLSessionContext getSessionContext() { return delegate.getSessionContext(); } @Override public void setHandshakeApplicationProtocolSelector(HandshakeApplicationProtocolSelector selector) { delegate.setHandshakeApplicationProtocolSelector(selector); } @Override public HandshakeApplicationProtocolSelector getHandshakeApplicationProtocolSelector() { return delegate.getHandshakeApplicationProtocolSelector(); } @Override public String getApplicationProtocol() { return delegate.getApplicationProtocol(); } @Override public String getNegotiatedApplicationProtocol() { return delegate.getNegotiatedApplicationProtocol(); } }
2. 配置Spring Boot使用自定义SSLEngine
针对默认Tomcat容器,自定义连接器配置替换原生SSLEngine:
import org.apache.coyote.http11.AbstractHttp11Protocol; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.net.ssl.SSLEngine; @Configuration public class TomcatSSLConfig { @Bean public WebServerFactoryCustomizer<TomcatServletWebServerFactory> sslEngineCustomizer() { return factory -> factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> { AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler(); protocol.setSSLEngineImplementation(new CustomSSLEngineImpl()); }); } private static class CustomSSLEngineImpl extends org.apache.tomcat.util.net.jsse.JSSEImplementation { @Override public SSLEngine createSSLEngine() { return new FingerprintSSLEngine(super.createSSLEngine()); } @Override public SSLEngine createSSLEngine(String host, int port) { return new FingerprintSSLEngine(super.createSSLEngine(host, port)); } } }
3. 在Rest控制器中获取TLS指纹
通过HttpServletRequest获取当前请求的SSLSession,取出存储的指纹:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.net.ssl.SSLSession; @RestController public class FingerprintController { @GetMapping("/tls-fingerprint") public String getTlsFingerprint(HttpServletRequest request) { SSLSession sslSession = (SSLSession) request.getAttribute("javax.servlet.request.ssl_session"); if (sslSession == null) { return "未检测到SSL会话"; } String fingerprint = (String) sslSession.getValue("TLS_FINGERPRINT"); return "TLS指纹: " + (fingerprint != null ? fingerprint : "未生成"); } }
注意事项
- 生产环境需替换示例中的
generateTlsFingerprint方法为标准指纹算法(如JA3),可使用成熟第三方库实现 - 若使用Netty/WebFlux容器,需自定义
SslContext来注入自定义SSLEngine,逻辑类似但实现细节不同 - 确保Spring Boot SSL配置正确(如
server.ssl.key-store、server.ssl.key-store-password等),否则SSLEngine无法正常初始化
内容的提问来源于stack exchange,提问作者Vivek
相关产品推荐
相关产品推荐

