Android 6(API23)使用OkHttp出现SSLHandshakeException问题求助
解决Android 6(API23)上OkHttp的证书信任异常问题
嘿,我刚帮朋友搞定过几乎一模一样的问题!作为网络新手碰到这种证书错误确实头大,我给你拆解下原因和靠谱的解决方案:
为什么会出现这个问题?
Android 7(API24)及以上系统在证书信任机制上做了优化,比如会自动补全不完整的服务器证书链;但Android 6及以下系统没这个能力,如果你的服务器配置的证书链不完整(比如缺了中间证书),或者用了Android 6默认根证书库里没有的根证书,就会触发这个CertPathValidatorException。
另外如果你的OkHttp版本太高,在旧系统上也可能有适配小问题,但先从证书本身排查更靠谱。
解决方案(按优先级排序)
1. 修复服务器的证书链(最推荐,生产环境必用)
这是从根源解决问题的方法。你可以用SSL检测工具检查服务器的证书链是否完整——如果工具提示“证书链不完整”,联系服务器管理员补上缺失的中间证书就行。补全后,Android 6就能正常信任服务器证书,不用改App代码。
2. 给OkHttp添加自定义信任管理器(适合测试/临时方案)
如果暂时没法修改服务器,或者只是测试用,可以让OkHttp专门信任你的服务器证书。步骤如下:
第一步:导出服务器证书
- 用浏览器打开你的服务器地址,点击地址栏的锁图标,导出证书(格式选X.509)
- 把导出的证书文件放到App的
res/raw目录下(没有raw目录就自己创建一个)
第二步:修改OkHttp的配置
在创建OkHttpClient的时候,加载证书并设置信任管理器:
// 加载raw目录里的证书文件(替换成你的证书文件名) InputStream certInputStream = getResources().openRawResource(R.raw.your_server_cert); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate serverCert = (X509Certificate) certFactory.generateCertificate(certInputStream); // 初始化KeyStore,把证书存进去 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, null); keyStore.setCertificateEntry("server_cert", serverCert); // 创建信任管理器 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); // 配置OkHttpClient OkHttpClient okHttpClient = new OkHttpClient.Builder() .sslSocketFactory(new SSLSocketFactoryCompat(serverCert), (X509TrustManager) trustManagers[0]) .build();
补充:兼容Android 6的SSLSocketFactoryCompat
因为Android 6的SSLSocketFactory有一些限制,你需要写一个兼容类:
public class SSLSocketFactoryCompat extends SSLSocketFactory { private final SSLSocketFactory delegate; public SSLSocketFactoryCompat(X509Certificate cert) throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 验证服务器证书是否和我们导入的一致 for (X509Certificate c : chain) { if (c.equals(cert)) { return; } } throw new CertificateException("Unknown server certificate"); } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }}, null); delegate = sslContext.getSocketFactory(); } @Override public String[] getDefaultCipherSuites() { return delegate.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } @Override public Socket createSocket() throws IOException { return delegate.createSocket(); } @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { return delegate.createSocket(s, host, port, autoClose); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return delegate.createSocket(host, port); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return delegate.createSocket(host, port, localHost, localPort); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return delegate.createSocket(host, port); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return delegate.createSocket(address, port, localAddress, localPort); } }
3. 绝对不要做的事!
别用网上那种“信任所有证书”的代码(比如跳过所有证书验证),那样会让你的App暴露在中间人攻击的风险里,生产环境绝对禁止这么做!
内容的提问来源于stack exchange,提问作者Basti
相关产品推荐
相关产品推荐

