Android支持Https总结

整合了一个工具类HttpsUtil,地址:

https://github.com/kabuzai/Android-Https

基本使用

  1. 全局支持Https只需在Application中调用
    HttpsUtil.initHttpsUrlConnection(this);

  2. 适用的网络请求:

  • 直接使用HttpUrlConnection的请求
  • 底层实现是HttpUrlConnection的框架(比如Volley(Api 9+)、ImageLoader、Glide等)
  1. OkHttp不适用,可以调用
    HttpsUtil.getHttpsOkHttpClient(Context context)
    来获取支持Https的OkHttpClient。

  2. 服务端证书放在Assets目录下
    修改证书名:
    private static final String[] CERTIFICATES = new String[]{"证书名.cer"};
    如有多个证书,向CERTIFICATES添加即可。

  3. 默认支持的是单向验证,安全性已足够

  • 如需要不进行验证,将CERTIFICATES元素清空即可,但这样存在极大的安全隐患,慎用!!
  • 如需要双向认证,需修改initHttpsUrlConnection的实现:
    public static void initHttpsUrlConnection(Context context) {
        InputStream[] certificates = getCertificates(context, CERTIFICATES);
        SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certificates, null, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
        if (certificates == null) {
            HttpsURLConnection.setDefaultHostnameVerifier(getUnSafeHostnameVerifier());
        }
    }

其中getSSLSocketFactory方法的后两个参数需要传入合适的值
具体实现可参考:
http://blog.csdn.net/lmj623565791/article/details/48129405

为什么Https请求需要这样额外配置?

简单来说,Https相比Http多了一层SSL验证,这个验证有很多步骤。其中有一步是对服务器证书的校验,而服务器证书的获得:

  • 可以由权威机构颁发,这些机构大多数都已经在Android设备的信任列表中,这样的证书不需要额外配置就能直接访问Https,那些不在设备信任列表的机构颁发的证书就需要进行配置。
    注意:不同Android设备的信任列表可能是不同的。
  • 可以是自签名证书,这种证书肯定无法通过默认的验证,需要自己实现验证方法,或者直接配置不验证,信任所有证书。
    注意:信任所有证书是存在极大的安全隐患的,这意味着你的请求也不会被加密,任何人都可以拦截你的请求并伪装成你的服务器与你通信,并记录你的信息比如登录账号和密码等。
  • 可以是由权威机构授权的中间机构颁发的中间证书,这种情况下进行SSL验证时服务器必须返回整个证书链,不能只返回根证书,否则也会验证失败。

深入了解可参考:
http://www.cnblogs.com/P_Chou/archive/2010/12/27/https-ssl-certification.html

其他可能出现的问题

SSL验证时会对证书的有效期进行验证,如果用户设备的时间不在有效期内也会验证失败。如果想忽略有效期,可以使用HttpsUtil中的NotValidateTimeTrustManager类:

private static class NotValidateTimeTrustManager implements X509TrustManager {

        private X509TrustManager defaultTrustManager;

        public NotValidateTimeTrustManager(X509TrustManager defaultTrustManager) {
            this.defaultTrustManager = defaultTrustManager;
        }

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

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException e) {
                e.printStackTrace();
                Throwable t = e;
                while (t != null) {
                    if (t instanceof CertificateExpiredException
                            || t instanceof CertificateNotYetValidException)
                        return;
                    t = t.getCause();
                }
                throw e;
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return defaultTrustManager.getAcceptedIssuers();
        }
    }

在checkServerTrusted方法中捕获了CertificateException,并不断向上寻找原因。如果发现是CertificateExpiredException(证书过期)或者CertificateNotYetValidException(证书还未生效)则通过验证,否则继续抛出异常。

使用方法:

private static TrustManager[] prepareTrustManager(InputStream... certificates) {
        ...
            return trustManagerFactory.getTrustManagers();
            // TODO: 2016/11/11 针对有效期异常导致校验失败的情况,目前没有完美的解决方案
//            TrustManager[] keyStoreTrustManagers = trustManagerFactory.getTrustManagers();
//            return getNotValidateTimeTrustManagers((X509TrustManager[]) keyStoreTrustManagers);
        ...
    }

将第一行注释,放开最后两行注释

但要注意,这个方案仍然有一定的安全隐患,因为查看底层进行SSL验证的源码后发现,对证书有效期的校验并不是最后的环节,例如在校验有效期通过后还会校验设备上的吊销证书列表,确认证书是否不在该列表中,其他还有一些自定义校验项。

上述方案在校验有效期异常后就会通过验证,漏掉了剩下的一些校验项,仍然存在一些隐患,如果安全级别要求非常高,并不推荐使用。

更多SSL验证问题可以参考:
https://developer.android.com/training/articles/security-ssl.html#MissingCa

    原文作者:卡布崽
    原文地址: https://www.jianshu.com/p/2f6ace079568
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞