Android開發 - Retrofit 2 使用自簽名的HTTPS證書進行API請求

羅伊德發表於2018-10-19

為了確保資料傳輸的安全,現在越來越多的應用使用Https的方式來進行資料傳輸,使用https有很多有點,比如:

  • HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網路協議,要比http協議安全,可防止資料在傳輸過程中不被竊取、改變,確保資料的完整性。
  • HTTPS是現行架構下最安全的解決方案,雖然不是絕對安全,但它大幅增加了中間人攻擊的成本。

但是即使使用HTTPS有很多有點,但是購買一個認證的HTTPS證照卻價格不菲,增加了初創企業和小團隊的開發成本。而且如上面所說,使用HTTPS並非絕對的安全,傳輸的資料並非沒有辦法獲取到,我的之前一篇部落格所使用的方法已成功擷取HTTPS的資料傳輸。有興趣的可以參考:使用Charles對Android App的https請求進行抓包

為了節約成本,我們可以選擇使用自簽名的HTTPS。 這種方式我們可以自己生成證照,不需要購買證照,但是使用這種方式的域名如果在瀏覽器中訪問的話,會有一個不安全的標識。但是如果用在客戶端上,就不會有這個問題,現在一些應用商店也要求APP上架時需要使用HTTPS的網路請求(比如AppStore),使用這種自簽名的HTTPS同樣也能過審。

本文我們介紹在Android上使用網路請求框架Retrofit 2來請求自簽名的API,關於如何生成HTTPS證照,我們將在另一篇部落格中進行說明,敬請期待。或者您可以自行查詢相關資料。

配置證照

比如我們生成的SLL證照檔案為"api_ssl_debug.cer",我們將其放到assets目錄下。這樣在配置Retrofit的時候就可以讀取該證照並用於API請求中。

配置Retrofit

本節我們介紹SSL證照在Retrofit中的配置,首先我們要定義兩個方法用來讀取證照,然後講該證照用於Retrofit中。

  1. 讀取證照內容到KeyStore中
    private static KeyStore getKeyStore(String fileName) {
        KeyStore keyStore = null;
        try {
            AssetManager assetManager = Utils.getApp().getAssets();
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = assetManager.open(fileName);
            Certificate ca;
            try {
                ca = cf.generateCertificate(caInput);
                Log.d("SslUtils", "ca=" + ((X509Certificate) ca).getSubjectDN());
            } finally {
                caInput.close();
            }

            String keyStoreType = KeyStore.getDefaultType();
            keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);
        } catch (Exception e) {
            Log.e("SslUtils", "Error during getting keystore", e);
        }
        return keyStore;
    }
複製程式碼
  1. 生成SSLContext以便用於Retrofit的配置中
    public static SSLContext getSslContextForCertificateFile(String fileName) {
        try {
            KeyStore keyStore = SslUtils.getKeyStore(fileName);
            SSLContext sslContext = SSLContext.getInstance("SSL");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext;
        } catch (Exception e) {
            String msg = "Error during creating SslContext for certificate from assets";
            Log.e("SslUtils", msg, e);
            throw new RuntimeException(msg);
        }
    }
複製程式碼
  1. 配置Retrofit
SSLContext sslContext = SslUtils.getSslContextForCertificateFile("api_ssl_debug.cer");

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
    throw new IllegalStateException("Unexpected default trust managers:"
                    + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                ...
                .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
                .hostnameVerifier((hostname, session) -> true)
                ...
                
mRetrofit = new Retrofit.Builder()
                ...
                build();
複製程式碼

以上的省略號為Retrofit的其它配置,可以根據工程需要進行配置。

總結

至此,使用Retrofit就可以進行自簽名的HTTPS的網路請求了,當然,伺服器端的API配置也需要進行HTTPS的配置,我將在服務端相關的部落格進行介紹,還有一點,如果請求的域名指定了埠,要將埠設定為443。如果沒有指定埠,請求時也會自動走443埠。

本文原始地址,如有更多疑問,請參考我的其它Android相關部落格:我的部落格地址

相關文章