Windows本地连接SSL Kafka遭遇PKIX路径构建失败问题求助
问题背景
在Windows本地通过Eclipse开发Java代码访问SSL加密的Kafka Topic,已获取下游团队提供的keystore和.cer证书文件。使用的SSL参数如下:
prop.put("security.protocol", "SSL"); prop.put("ssl.keystore.location",${unix or Windows path}); prop.put("ssl.keystore.password", password);
将Jar包部署到Unix服务器,通过java -cp命令运行时可正常访问Kafka Topic(keystore路径示例:/tmp/keystore.jks);但在Windows本地使用路径C:\\userID\\Desktop\\keystore.jks(文件已存在对应路径)访问时,出现以下错误:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Kafka调试日志显示已正确读取keystore文件,但连接仍失败:
ssl.keystore.location = C:\userID\Desktop\keystore.jks ssl.keystore.password = [hidden] ssl.keystore.type = JKS
已尝试的无效方案
- 使用
keytool命令将.cer证书导入本地Java环境的cacerts文件,因无Program Files目录下的管理员权限,出现访问被拒绝错误 - 在主类方法中设置系统属性:
System.setProperty("javax.net.ssl.keyStore","C:\\userID\\Desktop\\keystore.jks"); System.setProperty("javax.net.ssl.keyStorePassword",password);
- 通过JVM
-D参数传递上述系统属性,均未成功
附-Djavax.net.debug=ssl调试日志:
Updated debug logs from -Djavax.net.debug=ssl javax.net.ssl|FINE|01|main|2022-07-31 11:10:33.097 EDT|SSLCipher.java:438|jdk.tls.keyLimits: entry = AES/GCM/NoPadding KeyUpdate 2^37. AES/GCM/NOPADDING:KEYUPDATE = 137438953472 javax.net.ssl|SEVERE|01|main|2022-07-31 11:10:33.945 EDT|TransportContext.java:361|Fatal (CERTIFICATE_UNKNOWN): sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target ( "throwable" : { sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439) at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306) at sun.security.validator.Validator.validate(Validator.java:271) at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:312) at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:275) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:140) at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:630) at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:471) at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:367) at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:376) at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:479) at sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:990) at sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:977) at java.security.AccessController.doPrivileged(Native Method) at sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:924) at org.apache.kafka.common.network.SslTransportLayer.runDelegatedTasks(SslTransportLayer.java:336) at org.apache.kafka.common.network.SslTransportLayer.handshakeUnwrap(SslTransportLayer.java:417) at org.apache.kafka.common.network.SslTransportLayer.handshake(SslTransportLayer.java:270) at org.apache.kafka.common.network.KafkaChannel.prepare(KafkaChannel.java:69) at org.apache.kafka.common.network.Selector.pollSelectionKeys(Selector.java:360) at org.apache.kafka.common.network.Selector.poll(Selector.java:313) at org.apache.kafka.clients.NetworkClient.poll(NetworkClient.java:349) at org.apache.kafka.clients.consumer.internals.ConsumerNetworkClient.poll(ConsumerNetworkClient.java:226) at org.apache.kafka.clients.consumer.internals.ConsumerNetworkClient.poll(ConsumerNetworkClient.java:188) at org.apache.kafka.clients.consumer.internals.AbstractCoordinator.ensureCoordinatorReady(AbstractCoordinator.java:210) at org.apache.kafka.clients.consumer.internals.AbstractCoordinator.ensureCoordinatorReady(AbstractCoordinator.java:196) at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.poll(ConsumerCoordinator.java:281) at org.apache.kafka.clients.consumer.KafkaConsumer.pollOnce(KafkaConsumer.java:1030) at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:996) Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280) at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434) ... 30 more} ) javax.net.ssl|WARNING|01|main|2022-07-31 11:10:33.946 EDT|SSLEngineOutputRecord.java:173|outbound has closed, ignore outbound application data
需求
制作可连接SSL Kafka Topic的Windows Java可执行应用,分发给团队使用。
解决方案
1. 补充配置信任库(Truststore)
PKIX错误的核心是客户端无法信任Kafka服务器的证书,仅配置keystore(客户端身份证书)不足以完成信任链验证。需将下游提供的.cer证书导入独立的truststore文件:
keytool -importcert -file path/to/your/certificate.cer -keystore truststore.jks -alias kafka-trust
执行命令时设置truststore密码并记录,随后在Kafka客户端配置中添加以下参数:
prop.put("ssl.truststore.location", "C:\\userID\\Desktop\\truststore.jks"); prop.put("ssl.truststore.password", "your-truststore-password");
2. 嵌入证书文件,使用类路径读取
为方便团队分发,将keystore和truststore放入项目资源目录,打包后通过类路径读取,避免依赖本地绝对路径:
// 从类路径加载证书文件 String keystorePath = Thread.currentThread().getContextClassLoader().getResource("keystore.jks").getPath(); String truststorePath = Thread.currentThread().getContextClassLoader().getResource("truststore.jks").getPath(); prop.put("ssl.keystore.location", keystorePath); prop.put("ssl.keystore.password", "your-keystore-password"); prop.put("ssl.truststore.location", truststorePath); prop.put("ssl.truststore.password", "your-truststore-password");
打包时确保两个证书文件被包含在Jar包的资源目录中。
3. 构建带依赖的可执行Jar
使用构建工具生成包含所有依赖的可执行Jar,方便团队直接运行:
- Maven:配置
maven-assembly-plugin,指定主类,打包为jar-with-dependencies类型 - Gradle:使用
shadowJar插件生成包含依赖的可执行Jar
4. 临时绕过证书验证(仅测试环境)
如果是临时测试场景,可自定义TrustManager跳过证书验证(生产环境绝对禁止使用):
// 自定义TrustManager,跳过证书验证 TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} } }; // 初始化SSL上下文 SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); // 配置Kafka客户端使用自定义SSL上下文(需结合Kafka的SslEngineFactory实现)
关键说明
- Unix环境能正常运行,大概率是因为服务器的系统cacerts已包含Kafka服务器的根证书,而Windows本地环境缺失该信任链
- 必须同时配置keystore(客户端身份)和truststore(信任服务器证书),PKIX错误本质是信任链不完整
- 使用独立的truststore而非系统cacerts,可避免权限问题,同时便于应用分发
内容的提问来源于stack exchange,提问作者username89

