OkHttp配置HTTPS訪問+服務器部署

1 概述

OkHttp配置HTTPS訪問,核心爲如下三個部分:html

  • sslSocketFactory()
  • HostnameVerifier
  • X509TrustManager

第一個是ssl套接字工廠,第二個用來驗證主機名,第三個是證書信任器管理類.經過OkHttp實現HTTPS訪問須要本身實現以上三部分.另外還簡單說起了服務器端的部署,用的是Tomcat9,最後是一些常見問題的可能解決方案.前端

2 OkHttp介紹

OkHttp是一款開源的處理網絡請求的輕量級框架,有Square公司貢獻,用於替代HttpUrlConnection與Apache HttpClient,目前Github上有36.4k的star.優勢有java

  • 共享socket,HTTP/2支持全部鏈接到同一個主機的請求共享socket
  • 鏈接池能夠減小請求延遲
  • 緩存響應數據減小重複的網絡請求
  • 自動處理gzip壓縮

總的來講OkHttp是一款支持get/post請求,支持文件上傳/下載的優秀的HTTP框架.git

3 準備工做

  • 一臺服務器
  • 一個域名
  • 一個證書

什麼?都沒有?買!
固然證書能夠不用買,可使用openssl之類的工具生成,不過自簽名的證書後面驗證的時候會有點麻煩,建議仍是購買.github

4 OkHttp部分

4.1 暴力方案

public static String test() {
    OkHttpClient client = new OkHttpClient.Builder()
        .sslSocketFactory(createSSLSocketFactory(), new TrustAllCerts())
        .hostnameVerifier(new TrustAllHostnameVerifier()).build();

    String url = "https://xxxxxxx";   //修改爲本身的url
    Request request = new Request.Builder().url(url).build();
    Call call = build.newCall(request);
    Response response = call.execute();
    if(response.body() != null)
    {
        String result = response.body().string();
        //處理result
    }
}

private static class TrustAllCerts implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}
}

private static class TrustAllHostnameVerifier implements HostnameVerifier {
    public boolean verify(String hostname, SSLSession session) { return true; }
}

private static SSLSocketFactory createSSLSocketFactory() {
    SSLSocketFactory ssfFactory = null;
    try {
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom());
        ssfFactory = sc.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ssfFactory;
}

這是一種暴力的方案,看類名就知道了,信任全部的證書與主機:web

public boolean verify(String hostname, SSLSession session) { return true; }

這個方法直接返回true,也就是信任全部的主機.後端

public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

這裏兩個check函數沒有作任何的工做,表示接受任意的客戶端與服務端的證書.這樣寫的話至關因而使用了一個沒用的TrustManager,這樣還不如不加密,不推薦使用.數組

4.2 推薦方案

從兩方面入手修改,一是從X509TrustManager入手,二是從HostnameVerifier入手.緩存

4.2.1 HostnameVerifier

先說個簡單的,這裏主要是驗證主機名,簡單的話,能夠以下實現:tomcat

HostnameVerifier hnv = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        if("www.test.com".equals(hostname)){  
            return true;  
        } 
        else {  
            HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
            return hv.verify(hostname, session);
        }
    }
};

這裏驗證主機名是www.test.com就返回true,實現得比較簡單,業務複雜的話能夠結合配置中心,黑/白名單等動態校驗.

4.2.2 X509TrustManager

這裏其實有兩種方式,一種是以流的方式添加信任證書:

private static 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");
    }

    char[] password = "password".toCharArray(); // 這裏可使用任意密碼
    KeyStore keyStore = newEmptyKeyStore(password);
    int index = 0;
    for (Certificate certificate : certificates) {
        String certificateAlias = Integer.toString(index++);
        keyStore.setCertificateEntry(certificateAlias, certificate);
    }

    // Use it to build an X509 trust manager.
    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];
}

完整代碼見文末.這裏把工具類的方法實現成了靜態,調用時能夠直接:

OKHTTP.send("https://xxxxx");

另外一種方式是直接自定義一個TrustManager,重寫裏面的三個方法:

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{
    new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {}

        @Override
        public void checkServerTrusted(X509Certificate[] chain,String authType) throws CertificateException {
            for (X509Certificate cert : chain) {
                // Make sure that it hasn't expired.
                cert.checkValidity();
                // Verify the certificate's public key chain.
                try {
                    cert.verify(((X509Certificate) ca).getPublicKey());
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                } catch (InvalidKeyException e) {
                    e.printStackTrace();
                } catch (NoSuchProviderException e) {
                    e.printStackTrace();
                } catch (SignatureException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
}, null);

第一個方法爲

@Override
public void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {}

該方法檢查客戶端的證書,因爲不須要對客戶端進行認證,默認便可.
第二個方法爲

@Override
public void checkServerTrusted(X509Certificate[] chain,String authType)

該方法檢查服務器的證書,若不信任該證書則拋出異常,經過本身實現該方法能夠信任任何本身指定的證書,不作任何處理的話,不會拋出任何異常,至關於信任全部證書.這裏檢查了證書是否過時以及證書的簽名是否匹配.
第三個方法爲

@Override
public X509Certificate[] getAcceptedIssuers() {
    return new X509Certificate[0];
}

返回受信任的X509證書數組.

這種方法筆者沒有試過,僅供參考.

5 服務器部署

服務器用的是Tomcat,簡單介紹一下部署.

5.1 上傳工程

後端處理用的Spring Boot的工程,就不演示了,使用IDEA打成war包後上傳到webapps下便可.

5.2 Tomcat配置

重點說一下Tomcat的配置,首先須要一個域名,修改conf/server.xml文件,找到默認的名叫localhost的Host:
在這裏插入圖片描述
而後直接複製Host標籤,把name修改爲本身的域名便可.
在這裏插入圖片描述
而後是證書的配置,筆者的證書在某某雲上購買的,這裏提供了幾種格式的證書下載:
在這裏插入圖片描述
Tomcat的是兩個文件,一個是pfx文件,一個是密碼文件,把pfx文件上傳到服務器的Tomcat後,繼續修改server.xml,大約87行左右的位置(Tomcat版本9.0.33):
在這裏插入圖片描述
修改以下:
在這裏插入圖片描述
添加了scheme,secure,keystoreFile,keystoreType,keystorePass,clientAuth,sslProtocol配置,同時去掉裏面的<SSLHostConfig>,keystoreFile是剛纔的pfx文件,採用絕對路徑,keystorePass是密碼.
另外默認的端口爲8443,這裏修改爲了8123.
重啓Tomcat後輸入

https://www.test.com:port

進行測試
在這裏插入圖片描述
這樣就成功了.

6 驗證與源碼

這個由於沒有完整的Demo很難作驗證,具體來講前端用的OkHttp核心都介紹了,後端的話服務器Tomcat也介紹了,用Spring Boot作個Demo應該不難.
這裏只給出了工具類OKHTTP的源碼:
github

7 常見問題

7.1 Tomcat HTTPS沒法訪問

  • 證書文件錯誤,不過這個可能性比較少.
  • 配置錯誤,請檢查配置文件是否正確,能夠ps -ef | grep tomcat查看Tomcat是否開啓以及查看logs/catalina.out日誌.
  • 端口錯誤,訪問的端口須要與<Connector>中的端口對應(Tomcat默認的HTTPS端口爲8443,筆者竟然當作了8433,而後netstat 無數次都沒有看到被監聽...)
  • 安全組/防火牆問題,雲服務器的話須要在安全組配置中開啓相應端口,同時應查看有沒有把某個ip列入黑名單致使沒法訪問.防火牆的話這裏主要指iptables,若是沒有開啓的話不須要理會,若是開啓的話須要開放對應端口.

7.2 OkHttp HTTPS沒法訪問

  • 沒法讀取證書文件:須要把證書文件放在工程對應路徑下讀取,好比AS中放在assets下而後使用getAssets().open("xxx.xxx")獲取,Maven工程的話放在resources下直接使用FileInputStream獲取.
  • singed fields invalid:
    在這裏插入圖片描述
    證書文件格式錯誤,使用.crt/.pem等證書.
  • Signature does not match:這個有多是使用openssl自生成證書在驗證的時候出現的異常,可能的解決辦法是轉換證書的格式,若是不行就從新生成一次證書.

8 參考連接

1.蘋果核 - Android App 安全的HTTPS 通訊
2.Android OkHttp實現HTTPS訪問,支持Android 4.X系統HTTPS訪問
3.Android使用OkHttp請求自簽名的https網站

相關文章
相關標籤/搜索