Java 17 Spring Boot集成Jetty的mTLS微服务自定义SSL握手失败日志实现求助
Spring Boot 17 + Jetty mTLS 自定义SSL握手失败日志实现
以下是完整的代码实现步骤,涵盖Jetty容器配置、mTLS启用以及自定义握手失败处理器:
1. 调整依赖(排除Tomcat,引入Jetty)
在pom.xml中替换默认的Tomcat容器为Jetty:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-ssl</artifactId> </dependency> </dependencies>
2. 自定义SSL握手失败处理器
实现Jetty的SslHandshakeListener接口,根据异常类型分类处理握手失败事件,替换为你需要的自定义日志/错误处理逻辑:
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.ssl.SslContextFactory; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import java.util.logging.Level; import java.util.logging.Logger; public class CustomSslHandshakeFailureHandler implements SslContextFactory.SslHandshakeListener { private static final Logger CUSTOM_LOGGER = Logger.getLogger(CustomSslHandshakeFailureHandler.class.getName()); @Override public void handshakeStarted(EndPoint endPoint, SSLSession sslSession) { // 可选:记录握手开始事件 CUSTOM_LOGGER.info("SSL握手开始,客户端地址:" + endPoint.getRemoteAddress()); } @Override public void handshakeSucceeded(EndPoint endPoint, SSLSession sslSession) { // 可选:记录握手成功事件 try { String peerPrincipal = sslSession.getPeerPrincipal().getName(); CUSTOM_LOGGER.info("SSL握手成功,客户端证书主体:" + peerPrincipal); } catch (Exception e) { CUSTOM_LOGGER.warning("无法获取客户端证书主体信息"); } } @Override public void handshakeFailed(EndPoint endPoint, SSLSession sslSession, Throwable failure) { String clientAddr = endPoint.getRemoteAddress().toString(); String errorMsg; if (failure instanceof SSLHandshakeException) { String errMsg = failure.getMessage(); if (errMsg.contains("unable to find valid certification path")) { errorMsg = "客户端证书未被信任CA识别"; } else if (errMsg.contains("no cipher suites in common")) { errorMsg = "密钥套件不匹配"; } else if (errMsg.contains("Bad certificate")) { errorMsg = "客户端证书无效/过期"; } else { errorMsg = "SSL握手失败:" + errMsg; } } else { errorMsg = "SSL握手异常:" + failure.getClass().getSimpleName() + " - " + failure.getMessage(); } // 这里可以替换为自定义逻辑:写入数据库、发送告警、存储到自定义日志系统等 CUSTOM_LOGGER.log(Level.SEVERE, "客户端[" + clientAddr + "] " + errorMsg, failure); } }
3. 配置Jetty mTLS与自定义处理器
创建Spring配置类,自定义Jetty服务器的SSL连接工厂,启用mTLS并添加自定义握手监听器:
import org.eclipse.jetty.server.*; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JettyMtlsConfiguration { @Bean public WebServerFactoryCustomizer<JettyServletWebServerFactory> jettyMtlsCustomizer() { return factory -> { factory.addServerCustomizers(server -> { // 配置SSL上下文工厂,启用mTLS SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); // 读取配置文件中的SSL参数(也可直接硬编码,建议用配置文件) sslContextFactory.setKeyStorePath("classpath:server-keystore.jks"); sslContextFactory.setKeyStorePassword("server-keystore-pass"); sslContextFactory.setKeyManagerPassword("server-key-pass"); sslContextFactory.setTrustStorePath("classpath:truststore.jks"); sslContextFactory.setTrustStorePassword("truststore-pass"); // 强制要求客户端提供证书(mTLS模式) sslContextFactory.setNeedClientAuth(true); // 添加自定义握手失败监听器 sslContextFactory.addSslHandshakeListener(new CustomSslHandshakeFailureHandler()); // 创建SSL连接工厂与HTTP连接工厂 SslConnectionFactory sslConnFactory = new SslConnectionFactory(sslContextFactory, "http/1.1"); HttpConnectionFactory httpConnFactory = new HttpConnectionFactory(new HttpConfiguration()); // 配置HTTPS连接器 ServerConnector httpsConnector = new ServerConnector(server, sslConnFactory, httpConnFactory); httpsConnector.setPort(8443); // 替换默认连接器 server.setConnectors(new Connector[]{httpsConnector}); }); }; } }
4. 配置文件补充(application.yml)
将SSL参数配置到application.yml中,方便维护:
server: port: 8443 ssl: key-store: classpath:server-keystore.jks key-store-password: server-keystore-pass key-password: server-key-pass trust-store: classpath:truststore.jks trust-store-password: truststore-pass client-auth: need
5. 证书准备说明
server-keystore.jks:服务端的证书密钥库,包含服务端SSL证书与私钥truststore.jks:信任库,包含用于验证客户端证书的CA根证书或中间证书- 可以用
keytool命令生成或导入证书:# 生成服务端密钥库 keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -storetype JKS -keystore server-keystore.jks -validity 3650 # 导入CA证书到信任库 keytool -importcert -alias ca-cert -file ca.crt -storetype JKS -keystore truststore.jks
验证测试
使用curl发送无效请求,验证自定义处理器是否记录对应错误:
# 无证书请求(触发"客户端证书未提供"相关错误) curl -v https://localhost:8443/api/test # 使用无效证书请求 curl -v --cert invalid-client.crt --key invalid-client.key --cacert ca.crt https://localhost:8443/api/test
内容的提问来源于stack exchange,提问作者Colin Schofield




