0

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

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

谢谢

4

1 回答 1

1

我终于找到了解决方案,感谢Javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: Failure in SSL library, 通常是协议错误

首先下载有问题网站的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: https://stackoverflow.com/questions/29916962/javax-net-ssl-sslhandshakeexception-javax-net-ssl-sslprotocolexception-ssl-han
        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,则需要执行以下操作:

_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();
    }
});
于 2019-11-23T17:51:25.397 回答