Postman证书请求正常,Java OkHttpClient遇SunCertPathBuilderException错误
问题:Postman配置证书可正常请求,OkHttpClient代码执行失败
报错信息
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
相关Java代码
import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.*; import java.security.cert.*; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; public class Main { private static X509Certificate loadCertificateFromFile(File file) throws CertificateException, IOException { FileInputStream inputStream = new FileInputStream(file); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream); inputStream.close(); return certificate; } private static PrivateKey loadPrivateKeyFromFile(File file) throws GeneralSecurityException, IOException { FileInputStream inputStream = new FileInputStream(file); byte[] keyBytes = new byte[inputStream.available()]; inputStream.read(keyBytes); inputStream.close(); String keyBytesStr = new String(keyBytes) .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s", ""); // Assuming the private key is in PKCS#8 format PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyBytesStr)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(keySpec); } public static void main(String[] args) throws GeneralSecurityException, IOException { File crtFile = new File("/oath/chain.crt"); File keyFile = new File("/path/private.key"); File serverFile = new File("/path/abc.com.pem"); X509Certificate certificate = loadCertificateFromFile(crtFile); X509Certificate serverCertificate = loadCertificateFromFile(serverFile); // Load the private key from KEY file PrivateKey privateKey = loadPrivateKeyFromFile(keyFile); // Load the certificate and private key into KeyStore KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(null, null); keyStore.setKeyEntry("alias", privateKey, null, new X509Certificate[] {certificate}); keyStore.setCertificateEntry("server", serverCertificate); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); // Create SSLContext and configure it with KeyManager and TrustManager SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); // Create OkHttpClient with the configured SSLContext OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory( sslContext.getSocketFactory(), (X509TrustManager) trustManagerFactory.getTrustManagers()[0]) .build(); // Make a request using the OkHttpClient Request request = new Request.Builder().url("<URL>").build(); try (Response response = client.newCall(request).execute()) { // Process the response System.out.println(response.code()); System.out.println(response.body().string()); } } }
问题原因及修复方案
核心问题分析
- 未配置KeyManager:代码中
sslContext.init(null, ...)传入null作为KeyManager数组,导致客户端未加载自身证书和私钥完成双向认证,而Postman会自动处理证书链逻辑。 - 证书链加载不完整:当前
loadCertificateFromFile方法只能读取单个证书,若chain.crt是包含根证书、中间证书的完整链式文件,会导致信任链断裂。 - 信任库配置错误:仅添加服务端单个证书无法形成完整信任链,需要加入服务端证书对应的根CA证书。
修复步骤
1. 配置KeyManagerFactory加载客户端证书和私钥
创建并初始化KeyManagerFactory,将其传入SSLContext以完成客户端证书认证:
// 初始化KeyManagerFactory KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "".toCharArray()); // 若KeyStore设置了密码,替换为空字符串
修改SSLContext初始化代码:
SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
2. 修改证书加载方法,支持读取完整证书链
替换原单个证书加载方法,读取链式文件中的所有证书:
private static X509Certificate[] loadCertificateChainFromFile(File file) throws CertificateException, IOException { FileInputStream inputStream = new FileInputStream(file); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(inputStream); inputStream.close(); return certificates.stream() .filter(cert -> cert instanceof X509Certificate) .map(cert -> (X509Certificate) cert) .toArray(X509Certificate[]::new); }
在main方法中替换证书加载逻辑:
X509Certificate[] certificateChain = loadCertificateChainFromFile(crtFile);
设置KeyStore条目时传入完整证书链:
keyStore.setKeyEntry("alias", privateKey, null, certificateChain);
3. 替换信任库中的服务端证书为根CA证书
将serverFile指向服务端证书对应的根CA证书文件,确保信任链完整:
File caFile = new File("/path/root-ca.pem"); // 替换为根CA证书路径 X509Certificate caCertificate = loadCertificateFromFile(caFile);
完整修复后的代码示例
import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.Collection; public class Main { private static X509Certificate[] loadCertificateChainFromFile(File file) throws CertificateException, IOException { FileInputStream inputStream = new FileInputStream(file); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(inputStream); inputStream.close(); return certificates.stream() .filter(cert -> cert instanceof X509Certificate) .map(cert -> (X509Certificate) cert) .toArray(X509Certificate[]::new); } private static X509Certificate loadCertificateFromFile(File file) throws CertificateException, IOException { FileInputStream inputStream = new FileInputStream(file); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream); inputStream.close(); return certificate; } private static PrivateKey loadPrivateKeyFromFile(File file) throws GeneralSecurityException, IOException { FileInputStream inputStream = new FileInputStream(file); byte[] keyBytes = new byte[inputStream.available()]; inputStream.read(keyBytes); inputStream.close(); String keyBytesStr = new String(keyBytes) .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s", ""); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyBytesStr)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(keySpec); } public static void main(String[] args) throws GeneralSecurityException, IOException { File crtFile = new File("/oath/chain.crt"); File keyFile = new File("/path/private.key"); File caFile = new File("/path/root-ca.pem"); // 替换为根CA证书路径 X509Certificate[] certificateChain = loadCertificateChainFromFile(crtFile); X509Certificate caCertificate = loadCertificateFromFile(caFile); PrivateKey privateKey = loadPrivateKeyFromFile(keyFile); // 加载客户端证书和私钥到KeyStore KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(null, null); keyStore.setKeyEntry("client-alias", privateKey, null, certificateChain); keyStore.setCertificateEntry("ca-alias", caCertificate); // 初始化TrustManagerFactory TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); // 初始化KeyManagerFactory KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "".toCharArray()); // 若有密码请替换 // 创建SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom()); // 构建OkHttpClient OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagerFactory.getTrustManagers()[0]) .build(); // 发起请求 Request request = new Request.Builder().url("<你的请求URL>").build(); try (Response response = client.newCall(request).execute()) { System.out.println(response.code()); System.out.println(response.body().string()); } } }
额外注意事项
- 确认私钥格式为PKCS#8:若私钥开头是
-----BEGIN RSA PRIVATE KEY-----(PKCS#1格式),需用命令转换:openssl pkcs8 -topk8 -inform PEM -in private.key -outform PEM -nocrypt - 检查文件路径权限:确保程序能读取证书和密钥文件
- 私钥密码:若KeyStore设置了密码,需在
keyManagerFactory.init中传入对应密码
内容的提问来源于stack exchange,提问作者kaushik
相关产品推荐
相关产品推荐

