使用PKCS12证书调用API时遭遇PKIX路径构建失败问题求助
解决PKIX路径构建失败的SSL握手异常
问题描述
执行以下Java代码后,遇到SSL握手异常:
javax.net.ssl.SSLHandshakeException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
使用的代码:
import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.core5.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.ssl.SSLContexts; import org.apache.hc.core5.http.io.entity.StringEntity; import javax.net.ssl.SSLContext; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.KeyStore; public class CurlToJava { private static final String P12_FILE_PATH = "path/to/your/pfa-data-service.p12"; private static final String P12_PASSWORD = "PassPhrase"; private static final String TARGET_URL = "http://B17121-"+ "vip.nam.nsroot:100/monitoringDaily/api/download" + "/cs/cars/system%20Views/Obliger%20fund%20Details" + "(as69734_202405080737)?soeId=system&delimiter=%7C" + "&addHeadandTail=true&filename=testDownload"; private static final String AUTH_TOKEN = "0cb93babee5f6370007632b93365109a8"; private static SSLContext createSSLContext() throws Exception { // Load the PKCS12 KeyStore KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream keyStoreInputStream = new FileInputStream(P12_FILE_PATH)) { keyStore.load(keyStoreInputStream, P12_PASSWORD.toCharArray()); } return SSLContexts.custom() .loadKeyMaterial(keyStore, P12_PASSWORD.toCharArray()) .build(); } public static void main(String[] args) { try { SSLContext sslContext = createSSLContext(); SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager( PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory(sslSocketFactory) .build() ) .build(); HttpPost httpPost = new HttpPost(TARGET_URL); // Set your headers httpPost.setHeader("accept", "*/*"); httpPost.setHeader("AUTH_TOKEN", AUTH_TOKEN); httpPost.setHeader("Content-type", "application/json"); // Execute HTTP POST request try (CloseableHttpResponse response = httpClient.execute(httpPost); InputStream responseStream = response.getEntity().getContent(); OutputStream outputStream = new FileOutputStream("testDownload.csv")) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = responseStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } System.out.println("Response Code: " + response.getCode()); System.out.println("Response downloaded to testDownload.csv"); } } catch (Exception e) { e.printStackTrace(); } } }
问题原因
当前代码仅加载了客户端证书(PKCS12)用于身份验证,但未配置信任服务器端的SSL证书。JVM默认会从系统信任库(如cacerts)中查找可信证书,如果服务器证书不在默认信任库,就会抛出PKIX路径构建失败的异常。
解决方案
方案1:将服务器证书导入JVM默认信任库
- 获取服务器的SSL证书:
- 使用
openssl命令导出证书:openssl s_client -connect B17121-vip.nam.nsroot:100 < /dev/null | openssl x509 -outform PEM > server-cert.pem - 或通过浏览器访问目标HTTPS地址,导出证书为PEM格式。
- 使用
- 将证书导入JVM的
cacerts信任库:
(默认keytool -import -alias server-cert -file server-cert.pem -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeitcacerts密码是changeit,若已修改请使用对应密码)
方案2:在代码中加载自定义信任库
如果不想修改系统信任库,可以在代码中加载包含服务器证书的信任库(支持PKCS12或JKS格式),修改createSSLContext方法:
private static SSLContext createSSLContext() throws Exception { // 加载客户端证书密钥库 KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream keyStoreInputStream = new FileInputStream(P12_FILE_PATH)) { keyStore.load(keyStoreInputStream, P12_PASSWORD.toCharArray()); } // 加载信任库(包含服务器证书) KeyStore trustStore = KeyStore.getInstance("JKS"); String trustStorePath = "path/to/your/truststore.jks"; String trustStorePassword = "truststorePass"; try (FileInputStream trustStoreInputStream = new FileInputStream(trustStorePath)) { trustStore.load(trustStoreInputStream, trustStorePassword.toCharArray()); } return SSLContexts.custom() .loadKeyMaterial(keyStore, P12_PASSWORD.toCharArray()) .loadTrustMaterial(trustStore, null) // 加载信任库 .build(); }
方案3:临时跳过证书验证(仅测试环境使用)
注意:生产环境绝对禁止使用此方法,会带来严重安全风险
修改createSSLContext方法,创建信任所有证书的SSLContext:
private static SSLContext createSSLContext() throws Exception { // 加载客户端证书密钥库 KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream keyStoreInputStream = new FileInputStream(P12_FILE_PATH)) { keyStore.load(keyStoreInputStream, P12_PASSWORD.toCharArray()); } // 信任所有证书 return SSLContexts.custom() .loadKeyMaterial(keyStore, P12_PASSWORD.toCharArray()) .loadTrustMaterial((chain, authType) -> true) // 信任所有证书 .build(); }
同时,创建不验证主机名的SSLConnectionSocketFactory:
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext, (hostname, session) -> true // 跳过主机名验证 );
额外检查点
- 确认目标URL是否应为
https而非http:当前代码中URL是http,若服务器实际使用HTTPS,需改为https://B17121-vip.nam.nsroot:100/...,否则SSL配置不会生效。 - 确认PKCS12文件路径正确、密码无误、文件未损坏。
内容的提问来源于stack exchange,提问作者ravish kumar
相关产品推荐
相关产品推荐

