更新 HTTPS 证书后无法从 Android 应用程序访问网站

Can't access website from Android app after HTTPS certificate renewing

我有一个 Android 应用程序使用来自外部服务器(我无法控制)的 Web 服务。最近该服务器未能更新其 HTTPS 证书,并且有几个小时不可用。在此期间,我的应用程序的一些用户尝试使用这些服务,但自然失败了。 问题是现在服务器上的问题已经解决,这些用户仍然无法从我的应用程序访问该网站。一位用户甚至无法通过其移动设备的浏览器访问该网站,另一位用户仅在通过我的应用程序尝试时才被阻止。

我对 HTTPS 证书续订的经验有限,所以我想知道可能出了什么问题?这些设备似乎将过期证书保存在缓存中,并且不接受新证书。重新安装我的应用程序无法解决问题。

谢谢

感谢,我终于找到了解决方案。

首先下载问题网站的HTTPS证书(我是用Firefox做的)放到你的assets文件夹下。然后扩展应用程序,并添加以下内容:

public class MyApplication extends Application {

    private static SSLSocketFactory _sslSocketFactory = null;

    @Override
    public void onCreate() {

        super.onCreate();

        installSslIfNeeded();
        loadSslSocketFactoryIfNeeded();
    }

    @Nullable
    public static SSLSocketFactory getSslSocketFactory() {
        return _sslSocketFactory;
    }

    private void installSslIfNeeded() {

        // Install SSL certificates if needed:
        // See: 
        try {
            ProviderInstaller.installIfNeeded(this);
            SSLContext sslContext;
            sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, null);
            sslContext.createSSLEngine();
        }
        catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException | NoSuchAlgorithmException | KeyManagementException e) { e.printStackTrace(); }

    }

    private void loadSslSocketFactoryIfNeeded() {

        // Create a static SSL factory trusting the server's HTTPS certificate whose authority
        // is unknown for Android < 5
        // https://developer.android.com/training/articles/security-ssl

        if (_sslSocketFactory == null) {

            try {

                // Load certificate:
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                InputStream caInput = getAssets().open("theserver.crt");
                Certificate ca;
                //noinspection TryFinallyCanBeTryWithResources
                try { ca = cf.generateCertificate(caInput); }
                finally { caInput.close(); }

                // Create a KeyStore containing our trusted CAs
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                keyStore.load(null, null);
                keyStore.setCertificateEntry("ca", ca);

                // Create a TrustManager that trusts the CAs in our KeyStore
                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
                tmf.init(keyStore);

                // Create an SSLContext that uses our TrustManager
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, tmf.getTrustManagers(), null);

                _sslSocketFactory = sslContext.getSocketFactory();
            }
            catch (CertificateException e) { e.printStackTrace(); }
            catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
            catch (KeyStoreException e) { e.printStackTrace(); }
            catch (IOException e) { e.printStackTrace(); }
            catch (KeyManagementException e) { e.printStackTrace(); }
        }
    }
}

现在,如果您想从 REST API 下载 JSON 文件,您可以这样做:

static JSONObject readJsonFromUrl(String urlString) throws IOException, JSONException {

    // Tell the URLConnection to use a SocketFactory from our SSLContext
    URL url = new URL(urlString);
    HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
    urlConnection.setSSLSocketFactory(MyApplication.getSslSocketFactory());

    return readJsonFromInputStream(urlConnection.getInputStream());
}

如果您使用的是网络视图,则需要执行以下操作:

_webview.setWebViewClient(new WebViewClient() {

    [...]

    @Override
    public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {

        final AlertDialog.Builder builder = new AlertDialog.Builder(MyActivity.this);
        builder.setMessage(R.string.my_ssl_error_message);

        builder.setPositiveButton(R.string.common_continue, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                handler.proceed();
            }
        });

        builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                handler.cancel();
            }
        });

        builder.show();
    }
});