Android中OkHttp3处理HTTPS链接出现SSL握手异常求助
OkHttp3 解决 HTTPS 证书路径验证失败问题
我懂你碰到的这个麻烦——用OkHttp3访问https://cbsenet.nic.in的PDF文件时触发了SSL握手异常,而另一个域名的链接却能正常工作。这个错误的核心原因是Java默认的信任库里没有该网站的根证书,导致无法验证证书链的有效性,所以抛出了PKIX path building failed的异常。
先简单说下为什么两个链接表现不同:www.ugcnetonline.in用的是主流CA机构颁发的证书,这类证书已经被Java默认信任库收录,所以OkHttp能顺利完成验证;而cbsenet.nic.in的证书要么是自签名的,要么是由不被Java默认信任的CA颁发的,自然就过不了验证这一关。
下面给你两种解决方案,分别适用于测试和生产场景:
方案一:临时绕过SSL验证(仅测试环境使用)
这个方法是让OkHttp信任所有证书,跳过验证环节,绝对不能在生产环境用,会让你的应用面临中间人攻击的风险。代码示例如下:
import javax.net.ssl.*; import java.security.cert.X509Certificate; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class OkHttpSslTest { public static void main(String[] args) throws Exception { String url1 = "https://cbsenet.nic.in/cbsenet/PDFDEC2014/Paper%20III/D-01-3.pdf"; // 创建信任所有证书的TrustManager TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {} @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; // 初始化SSLContext SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new SecureRandom()); // 忽略主机名验证 HostnameVerifier hostnameVerifier = (hostname, session) -> true; // 配置OkHttpClient OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0]) .hostnameVerifier(hostnameVerifier) .build(); // 发起请求 Request request = new Request.Builder().url(url1).build(); try (Response response = client.newCall(request).execute()) { // 这里可以处理文件下载逻辑,比如写入本地 // response.body().byteStream(); System.out.println("请求成功"); } } }
方案二:导入证书到信任库(生产环境推荐)
这是安全合规的做法,分为两种实现方式:
方式1:导入到Java全局信任库
- 下载网站证书:打开浏览器访问
https://cbsenet.nic.in,点击地址栏的锁图标,查看证书详情,将证书导出为.cer格式(比如命名为cbsenet.cer)。 - 执行导入命令:打开终端,运行以下命令(替换
<JAVA_HOME>为你的JDK安装路径,<CERT_PATH>为证书文件所在路径):
Java默认信任库的密码是keytool -import -trustcacerts -alias cbsenet -file <CERT_PATH>/cbsenet.cer -keystore <JAVA_HOME>/jre/lib/security/cacertschangeit,输入密码后确认导入即可。 - 重启你的应用,此时OkHttp会自动信任该证书,无需修改代码就能正常访问url1。
方式2:在OkHttp中使用自定义信任库
如果你不想修改全局Java信任库,可以在OkHttp中单独配置信任该证书:
import javax.net.ssl.*; import java.io.FileInputStream; import java.security.KeyStore; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class OkHttpCustomSsl { public static void main(String[] args) throws Exception { String url1 = "https://cbsenet.nic.in/cbsenet/PDFDEC2014/Paper%20III/D-01-3.pdf"; String certPath = "path/to/your/cbsenet.cer"; // 替换为你的证书路径 // 加载证书文件 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate cert; try (FileInputStream fis = new FileInputStream(certPath)) { cert = (X509Certificate) certFactory.generateCertificate(fis); } // 创建包含该证书的KeyStore KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, null); keyStore.setCertificateEntry("cbsenet", cert); // 初始化TrustManagerFactory TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); // 配置SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), new SecureRandom()); // 构建OkHttpClient OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]) .build(); // 发起请求 Request request = new Request.Builder().url(url1).build(); try (Response response = client.newCall(request).execute()) { // 处理文件下载 System.out.println("请求成功,响应码:" + response.code()); } } }
这种方式只会让你的应用信任该证书,不会影响其他Java程序,非常适合生产环境使用。
内容的提问来源于stack exchange,提问作者hard coder
相关产品推荐
相关产品推荐

