美文网首页安卓技巧Android知识Android开发
Okhttp 访问自签名证书 HTTPS 地址解决方案

Okhttp 访问自签名证书 HTTPS 地址解决方案

作者: PandaQ404 | 来源:发表于2016-11-15 16:22 被阅读3646次

    Okhttp 访问 HTTPS 链接问题

    HTTPS 即以安全为目的的 HTTP 通道,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。一般情况下 CA 颁发的 https 证书是默认受浏览器信任的。okhttp框架也能直接访问这些网站拿到数据,但对于自签名证书,okhttp 默认是拒绝访问通过的。一般能直接访问的网站 Chrome 浏览器打开后会是一把绿色的锁,使用 okhttp 访问也能正常访问。使用自签名 https 的网站不会被浏览器信任,访问会提示危险。使用 okhttp 进行访问时会提示

    java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    

    解决办法

    解决办法有两种

    • 信任所有的证书(开发测试使用)
    • 导入自签名证书

    信任所有的证书

    信任所有的证书后可以成功解决掉不能访 https 自签名地址的问题,但这样也就失去了使用自签名的意义。开发过程中可以使用这种方式屏蔽掉自签名,需要使用到无法获得证书的地址的资源时也可以使用这种方式。

    实现代码

    import java.security.SecureRandom;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    import okhttp3.OkHttpClient;
    
    /**
     * Created by PandaQ on 2016/11/10.
     * 封装的支持Https连接的Okhttp客户端
     * email : 767807368@qq.com
     */
    
    public class HttpsUtils {
    
    private MyTrustManager mMyTrustManager;
    
        private SSLSocketFactory createSSLSocketFactory() {
            SSLSocketFactory ssfFactory = null;
            try {
                mMyTrustManager = new MyTrustManager();
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, new TrustManager[]{mMyTrustManager}, new SecureRandom());
                ssfFactory = sc.getSocketFactory();
            } catch (Exception ignored) {
                ignored.printStackTrace();
            }
    
            return ssfFactory;
        }
    
        //实现X509TrustManager接口
        public class MyTrustManager implements X509TrustManager {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
    
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
    
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }
    
        //实现HostnameVerifier接口
        private class TrustAllHostnameVerifier implements HostnameVerifier {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        }
    
        public OkHttpClient getTrustAllClient() {
            OkHttpClient.Builder mBuilder = new OkHttpClient.Builder();
            mBuilder.sslSocketFactory(createSSLSocketFactory(), mMyTrustManager)
                    .hostnameVerifier(new TrustAllHostnameVerifier());
            return mBuilder.build();
        }
    }
    
    

    实现步骤:
    ①创建实现 HostnameVerifier 接口的类并重写他的verify方法使其直接返回true;
    ②创建实现 X509TrustManager 接口的类并重写getAcceptedIssuers()使其返回 new X509Certificate[0];
    ③构建 SSL 工厂SSLSocketFactory即上面代码中的createSSLSocketFactory()方法;
    ④使用:OkHttpClient.Builder 中设置 sslSocketFactory(构建的工厂,实现的 TrustManager 类),此处的 TurstManager 需要保持跟 Factory 中使用的 TrustManager 是同一个实例。设置 hostnameVerifier 为构建的 HostnameVerifier;

    使用 OkhttpClientUtils.trustAll 得到的 okhttpClient 进行网络请求即可信任所有的 https 证书。

    导入自签名证书

    信任所有证书虽然能解决不能访问 https 服务器地址的问题,但安全性被忽视掉了。显然是不符合要求的,用 https 就是为了安全性跳过 验证也就失去了他的意义了。好在我们还有其他的方法来解决----导入自签名的证书。

    自签证书导入流程自签证书导入流程

    自签证书的导入流程如上图所示最终目的是为 okhttp 客户端设置一个包含自签证书信息的 sslsocketFactory。具体实现代码如下:

        /**
         * 对外提供的获取支持自签名的okhttp客户端
         *
         * @param certificate 自签名证书的输入流
         * @return 支持自签名的客户端
         */
        public OkHttpClient getTrusClient(InputStream certificate) {
            X509TrustManager trustManager;
            SSLSocketFactory sslSocketFactory;
            try {
                trustManager = trustManagerForCertificates(certificate);
                SSLContext sslContext = SSLContext.getInstance("TLS");
                //使用构建出的trustManger初始化SSLContext对象
                sslContext.init(null, new TrustManager[]{trustManager}, null);
                //获得sslSocketFactory对象
                sslSocketFactory = sslContext.getSocketFactory();
            } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
            return new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory, trustManager)
                    .build();
        }
    
        /**
         * 获去信任自签证书的trustManager
         *
         * @param in 自签证书输入流
         * @return 信任自签证书的trustManager
         * @throws GeneralSecurityException
         */
        private X509TrustManager trustManagerForCertificates(InputStream in)
                throws GeneralSecurityException {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            //通过证书工厂得到自签证书对象集合
            Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
            if (certificates.isEmpty()) {
                throw new IllegalArgumentException("expected non-empty set of trusted certificates");
            }
            //为证书设置一个keyStore
            char[] password = "password".toCharArray(); // Any password will work.
            KeyStore keyStore = newEmptyKeyStore(password);
            int index = 0;
            //将证书放入keystore中
            for (Certificate certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificate);
            }
            // Use it to build an X509 trust manager.
            //使用包含自签证书信息的keyStore去构建一个X509TrustManager
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, password);
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            return (X509TrustManager) trustManagers[0];
        }
    
        private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
            try {
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                InputStream in = null; // By convention, 'null' creates an empty key store.
                keyStore.load(null, password);
                return keyStore;
            } catch (IOException e) {
                throw new AssertionError(e);
            }
        }
    

    上述代码中的证书的 InputStream 可通过两种方式获得

    • 将证书放到工程中例如 assets 目录中,然后通过如下代码获得输入流
    InputStream inputStream = context.getAssets().open("srca.cer");
    
    • 通过命令行在证书所在目录运行
      keytool -printcert -rfc -file srca.cer srca.cer换成自己的证书名
      得到证书内的字符串内容,将字符串内容通过如下代码转换成 InputStream
    InputStream ins = new Buffer()
            .writeUtf8(comodoRsaCertificationAuthority)
            .writeUtf8(entrustRootCertificateAuthority)
            .inputStream();
    

    使用时通过 httpsUtils.getTrusClient(InputStream certificate)得到okhttpClient即可

    仅供参考

    没有 demo !贴上HttpUtils类的全部代码供大家参考

    import java.io.IOException;
    import java.io.InputStream;
    import java.security.GeneralSecurityException;
    import java.security.KeyStore;
    import java.security.SecureRandom;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.util.Arrays;
    import java.util.Collection;
    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.net.ssl.X509TrustManager;
    
    import okhttp3.OkHttpClient;
    
    /**
     * Created by PandaQ on 2016/11/10.
     * 封装的支持Https连接的Okhttp客户端
     * email : 767807368@qq.com
     */
    
    public class HttpsUtils {
    
        private MyTrustManager mMyTrustManager;
    
        private SSLSocketFactory createSSLSocketFactory() {
            SSLSocketFactory ssfFactory = null;
            try {
                mMyTrustManager = new MyTrustManager();
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, new TrustManager[]{mMyTrustManager}, new SecureRandom());
                ssfFactory = sc.getSocketFactory();
            } catch (Exception ignored) {
                ignored.printStackTrace();
            }
    
            return ssfFactory;
        }
    
        //实现X509TrustManager接口
        public class MyTrustManager implements X509TrustManager {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
    
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
    
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }
    
        //实现HostnameVerifier接口
        private class TrustAllHostnameVerifier implements HostnameVerifier {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        }
    
        public OkHttpClient getTrustAllClient() {
            OkHttpClient.Builder mBuilder = new OkHttpClient.Builder();
            mBuilder.sslSocketFactory(createSSLSocketFactory(), mMyTrustManager)
                    .hostnameVerifier(new TrustAllHostnameVerifier());
            return mBuilder.build();
        }
    
        /**
         * 对外提供的获取支持自签名的okhttp客户端
         *
         * @param certificate 自签名证书的输入流
         * @return 支持自签名的客户端
         */
        public OkHttpClient getTrusClient(InputStream certificate) {
            X509TrustManager trustManager;
            SSLSocketFactory sslSocketFactory;
            try {
                trustManager = trustManagerForCertificates(certificate);
                SSLContext sslContext = SSLContext.getInstance("TLS");
                //使用构建出的trustManger初始化SSLContext对象
                sslContext.init(null, new TrustManager[]{trustManager}, null);
                //获得sslSocketFactory对象
                sslSocketFactory = sslContext.getSocketFactory();
            } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
            return new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory, trustManager)
                    .build();
        }
    
        /**
         * 获去信任自签证书的trustManager
         *
         * @param in 自签证书输入流
         * @return 信任自签证书的trustManager
         * @throws GeneralSecurityException
         */
        private X509TrustManager trustManagerForCertificates(InputStream in)
                throws GeneralSecurityException {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            //通过证书工厂得到自签证书对象集合
            Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
            if (certificates.isEmpty()) {
                throw new IllegalArgumentException("expected non-empty set of trusted certificates");
            }
            //为证书设置一个keyStore
            char[] password = "password".toCharArray(); // Any password will work.
            KeyStore keyStore = newEmptyKeyStore(password);
            int index = 0;
            //将证书放入keystore中
            for (Certificate certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificate);
            }
            // Use it to build an X509 trust manager.
            //使用包含自签证书信息的keyStore去构建一个X509TrustManager
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, password);
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            return (X509TrustManager) trustManagers[0];
        }
    
        private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
            try {
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                InputStream in = null; // By convention, 'null' creates an empty key store.
                keyStore.load(null, password);
                return keyStore;
            } catch (IOException e) {
                throw new AssertionError(e);
            }
        }
    }
    
    

    觉得本文对你有帮助

    关注简书PandaQ404,持续分享中。。。
    Github主页

    相关文章

      网友评论

      • 浮华染流年:楼主放个demo啊
        PandaQ404: @9a129471f2eb 随便什么都行,是初始化的一个新的keystore的密码
        9a129471f2eb:char[] password = "password".toCharArray(); // Any password will work.
        这个"password" 是什么?
        PandaQ404: @浮华染流年 最后的那个类复制进去就能用。明天我放个demo吧

      本文标题:Okhttp 访问自签名证书 HTTPS 地址解决方案

      本文链接:https://www.haomeiwen.com/subject/krclpttx.html