Okhttp 訪問自簽名證書 HTTPS 地址解決方案

Okhttp 訪問 HTTPS 鏈接問題

HTTPS 即以安全為目的的 HTTP 通道,即 HTTP 下加入 SSL 層,HTTPS 的安全基礎是 SSL,因此加密的詳細內(nèi)容就需要 SSL。一般情況下 CA 頒發(fā)的 https 證書是默認受瀏覽器信任的。okhttp框架也能直接訪問這些網(wǎng)站拿到數(shù)據(jù),但對于自簽名證書,okhttp 默認是拒絕訪問通過的。一般能直接訪問的網(wǎng)站 Chrome 瀏覽器打開后會是一把綠色的鎖,使用 okhttp 訪問也能正常訪問。使用自簽名 https 的網(wǎng)站不會被瀏覽器信任,訪問會提示危險。使用 okhttp 進行訪問時會提示

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

解決辦法

解決辦法有兩種

  • 信任所有的證書(開發(fā)測試使用)
  • 導入自簽名證書

信任所有的證書

信任所有的證書后可以成功解決掉不能訪 https 自簽名地址的問題,但這樣也就失去了使用自簽名的意義。開發(fā)過程中可以使用這種方式屏蔽掉自簽名,需要使用到無法獲得證書的地址的資源時也可以使用這種方式。

實現(xiàn)代碼

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;
    }

    //實現(xiàn)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];
        }
    }

    //實現(xiàn)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();
    }
}

實現(xiàn)步驟:
①創(chuàng)建實現(xiàn) HostnameVerifier 接口的類并重寫他的verify方法使其直接返回true;
②創(chuàng)建實現(xiàn) X509TrustManager 接口的類并重寫getAcceptedIssuers()使其返回 new X509Certificate[0];
③構建 SSL 工廠SSLSocketFactory即上面代碼中的createSSLSocketFactory()方法;
④使用:OkHttpClient.Builder 中設置 sslSocketFactory(構建的工廠,實現(xiàn)的 TrustManager 類),此處的 TurstManager 需要保持跟 Factory 中使用的 TrustManager 是同一個實例。設置 hostnameVerifier 為構建的 HostnameVerifier;

使用 OkhttpClientUtils.trustAll 得到的 okhttpClient 進行網(wǎng)絡請求即可信任所有的 https 證書。

導入自簽名證書

信任所有證書雖然能解決不能訪問 https 服務器地址的問題,但安全性被忽視掉了。顯然是不符合要求的,用 https 就是為了安全性跳過 驗證也就失去了他的意義了。好在我們還有其他的方法來解決----導入自簽名的證書。

自簽證書導入流程
自簽證書導入流程

自簽證書的導入流程如上圖所示最終目的是為 okhttp 客戶端設置一個包含自簽證書信息的 sslsocketFactory。具體實現(xiàn)代碼如下:

    /**
     * 對外提供的獲取支持自簽名的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換成自己的證書名
    得到證書內(nèi)的字符串內(nèi)容,將字符串內(nèi)容通過如下代碼轉換成 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;
    }

    //實現(xiàn)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];
        }
    }

    //實現(xiàn)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,持續(xù)分享中。。。
Github主頁

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容