okhttp 实现 https 访问,支持 Android 4.X 系统 https 访问

cysk_zhang 8年前
   <p>去年在将公司项目进行组件化重构的时候使用了 Retrofit+OkHttp 做网络请求,由于我们公司的网络请求都是使用https访问,因此在封装好适用于我们项目的Retrofit+OkHttp网络请求框架后,就开始着手解决Https网络传输的问题。刚开始看了很多博文,也借鉴了这些博文的方法,结果没有一个能够实现Https访问。直到看到 <strong>鸿洋_</strong> 的 Android 一个改善的okHttp封装库 这篇博文的时候,我就很好奇他是怎么解决okhttp实现https访问的,然后我就在 okhttputils 主页,找到了 HttpsUtils 这个方法,不得不说还是大神的代码厉害,使用了这个工具后真的就请求成功了。</p>    <p>后来我又在这个工具中添加了主机名校验方法:</p>    <pre>  <code class="language-java">/**   * 主机名校验方法   */  public static HostnameVerifier getHostnameVerifier() {      return new HostnameVerifier() {          @Override          public boolean verify(String hostname, SSLSession session) {              return hostname.equalsIgnoreCase(session.getPeerHost());          }      };  }</code></pre>    <p>于是这个工具的使用方法就变成了这样:</p>    <pre>  <code class="language-java">HttpsUtil.SSLParams sslParams = HttpsUtil.getSslSocketFactory(Utils.getContext(), new int[0], R.raw.xxxx, "password");      okHttpClient = new OkHttpClient.Builder()              .connectTimeout(10000L, TimeUnit.MILLISECONDS)              .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)              .hostnameVerifier(HttpsUtil.getHostnameVerifier())              .addInterceptor(new LoggerInterceptor(null, true))              .build();</code></pre>    <p>但是,有一天一个哥们在Android4.3的系统上运行了下我们的APP,发现网络请求失败了,并且报了如下错误:</p>    <pre>  <code class="language-java">System.err: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x5ff1c438: Failure in SSL library, usually a protocol error  System.err: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x5cb66770:0x00000000)</code></pre>    <p>我一看到这个错误就知道我们HttpsUtils不管用了,因为在我没有解决Https网络请求的时候经常碰到handshake aborted,所以我就开始找https访问在Android4.3上为什么不管用了的原因,结果找到了这篇博文: <a href="/misc/goto?guid=4959738554360088721" rel="nofollow,noindex">关于Android4.x系统支持TLS1.2的解决方案</a> ,搞了半天原来是Android 4.X系统不支持TLSv1.1和TLSv1.2协议,于是我迅速的把这篇博文中的解决办法添加到了我的HttpsUtils中,并且做了Android系统判断,于是这个工具最终就成了下面这样:</p>    <pre>  <code class="language-java">import android.annotation.SuppressLint;  import android.content.Context;  import android.os.Build;  import android.support.annotation.RawRes;    import java.io.IOException;  import java.io.InputStream;  import java.net.InetAddress;  import java.net.Socket;  import java.net.UnknownHostException;  import java.security.KeyManagementException;  import java.security.KeyStore;  import java.security.KeyStoreException;  import java.security.NoSuchAlgorithmException;  import java.security.UnrecoverableKeyException;  import java.security.cert.Certificate;  import java.security.cert.CertificateException;  import java.security.cert.CertificateFactory;  import java.security.cert.X509Certificate;    import javax.net.ssl.HostnameVerifier;  import javax.net.ssl.KeyManager;  import javax.net.ssl.KeyManagerFactory;  import javax.net.ssl.SSLContext;  import javax.net.ssl.SSLSession;  import javax.net.ssl.SSLSocket;  import javax.net.ssl.SSLSocketFactory;  import javax.net.ssl.TrustManager;  import javax.net.ssl.TrustManagerFactory;  import javax.net.ssl.X509TrustManager;    /**   * HttpsUtils来自于鸿洋的: https://github.com/hongyangAndroid/okhttputils;   * 增加了主机名校验方法getHostnameVerifier();   * 其他参考的文章有:http://android.jobbole.com/83787/;   *   * Android 4.X 对TLS1.1、TLS1.2的支持参考了http://blog.csdn.net/joye123/article/details/53888252   */  public class HttpsUtil {        /**       * 包装的 SSL(Secure Socket Layer)参数类       */      public static class SSLParams {          public SSLSocketFactory sSLSocketFactory;          public X509TrustManager trustManager;      }        /**       * @param context        上下文       * @param certificatesId "XXX.cer" 文件 (文件位置res/raw/XXX.cer)       * @param bksFileId      "XXX.bks"文件(文件位置res/raw/XXX.bks)       * @param password       The certificate's password.       * @return SSLParams       */      public static SSLParams getSslSocketFactory(Context context, @RawRes int[] certificatesId, @RawRes int bksFileId, String password) {          if (context == null) {              throw new NullPointerException("context == null");          }          SSLParams sslParams = new SSLParams();          try {              TrustManager[] trustManagers = prepareTrustManager(context, certificatesId);              KeyManager[] keyManagers = prepareKeyManager(context, bksFileId, password);                //创建TLS类型的SSLContext对象,that uses our TrustManager              SSLContext sslContext = SSLContext.getInstance("TLS");                X509TrustManager x509TrustManager;              if (trustManagers != null) {                  x509TrustManager = new MyTrustManager(chooseTrustManager(trustManagers));              } else {                  x509TrustManager = new UnSafeTrustManager();              }              //用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书              sslContext.init(keyManagers, new TrustManager[]{x509TrustManager}, null);                //通过sslContext获取SSLSocketFactory对象              if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {                  /*Android 4.X 对TLS1.1、TLS1.2的支持*/                  sslParams.sSLSocketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());                  sslParams.trustManager = x509TrustManager;                  return sslParams;              }                sslParams.sSLSocketFactory = sslContext.getSocketFactory();              sslParams.trustManager = x509TrustManager;              return sslParams;          } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {              throw new AssertionError(e);          }      }          /**       * 主机名校验方法       */      public static HostnameVerifier getHostnameVerifier() {          return new HostnameVerifier() {              @Override              public boolean verify(String hostname, SSLSession session) {                  return hostname.equalsIgnoreCase(session.getPeerHost());              }          };      }        private static TrustManager[] prepareTrustManager(Context context, int[] certificatesId) {          if (certificatesId == null || certificatesId.length <= 0) {              return null;          }            try {              //创建X.509格式的CertificateFactory              CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");              // 创建一个默认类型的KeyStore,存储我们信任的证书              KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());              keyStore.load(null);              int index = 0;              for (int certificateId : certificatesId) {                  //从本地资源中获取证书的流                  InputStream cerInputStream = context.getResources().openRawResource(certificateId);                  String certificateAlias = Integer.toString(index++);                    //certificate是java.security.cert.Certificate,而不是其他Certificate                  //证书工厂根据证书文件的流生成证书Certificate                  Certificate certificate = certificateFactory.generateCertificate(cerInputStream);                  //将证书certificate作为信任的证书放入到keyStore中                  keyStore.setCertificateEntry(certificateAlias, certificate);                  try {                      if (cerInputStream != null)                          cerInputStream.close();                  } catch (IOException e) {                      e.printStackTrace();                  }              }                //TrustManagerFactory是用于生成TrustManager的,这里创建一个默认类型的TrustManagerFactory              TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());              //用我们之前的keyStore实例初始化TrustManagerFactory,这样trustManagerFactory就会信任keyStore中的证书              trustManagerFactory.init(keyStore);              return trustManagerFactory.getTrustManagers();          } catch (Exception e) {              e.printStackTrace();          }          return null;      }        private static KeyManager[] prepareKeyManager(Context context, @RawRes int bksFileId, String password) {        try {              KeyStore clientKeyStore = KeyStore.getInstance("BKS");              clientKeyStore.load(context.getResources().openRawResource(bksFileId), password.toCharArray());              KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());              keyManagerFactory.init(clientKeyStore, password.toCharArray());              return keyManagerFactory.getKeyManagers();            } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) {              e.printStackTrace();          }      return null;      }          private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {          for (TrustManager trustManager : trustManagers) {              if (trustManager instanceof X509TrustManager) {                  return (X509TrustManager) trustManager;              }          }          return null;      }          /**       * 客户端不对证书做任何检查;       * 客户端不对证书做任何验证的做法有很大的安全漏洞。       */      private static class UnSafeTrustManager implements X509TrustManager {            @SuppressLint("TrustAllX509TrustManager")          @Override          public void checkClientTrusted(X509Certificate[] chain, String authType)              throws CertificateException {          }            @SuppressLint("TrustAllX509TrustManager")          @Override          public void checkServerTrusted(X509Certificate[] chain, String authType)              throws CertificateException {          }            @Override          public X509Certificate[] getAcceptedIssuers() {              return new X509Certificate[]{};          }      }        private static class MyTrustManager implements X509TrustManager {          private X509TrustManager defaultTrustManager;          private X509TrustManager localTrustManager;            private MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {              //TrustManagerFactory是用于生成TrustManager的,创建一个默认类型的TrustManagerFactory              TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());              trustManagerFactory.init((KeyStore) null);              defaultTrustManager = chooseTrustManager(trustManagerFactory.getTrustManagers());              this.localTrustManager = localTrustManager;          }              @SuppressLint("TrustAllX509TrustManager")          @Override          public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {            }            @Override          public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {              try {                  defaultTrustManager.checkServerTrusted(chain, authType);              } catch (CertificateException ce) {                  localTrustManager.checkServerTrusted(chain, authType);              }          }              @Override          public X509Certificate[] getAcceptedIssuers() {              return new X509Certificate[0];          }      }           /**       * 自行实现SSLSocketFactory ,实现Android 4.X 对TLSv1.1、TLSv1.2的支持       */      private static class Tls12SocketFactory extends SSLSocketFactory {            private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};            final SSLSocketFactory delegate;            private Tls12SocketFactory(SSLSocketFactory base) {              this.delegate = base;          }            @Override          public String[] getDefaultCipherSuites() {              return delegate.getDefaultCipherSuites();          }            @Override          public String[] getSupportedCipherSuites() {              return delegate.getSupportedCipherSuites();          }            @Override          public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {              return patch(delegate.createSocket(s, host, port, autoClose));          }            @Override          public Socket createSocket(String host, int port) throws IOException, UnknownHostException {              return patch(delegate.createSocket(host, port));          }            @Override          public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {              return patch(delegate.createSocket(host, port, localHost, localPort));          }            @Override          public Socket createSocket(InetAddress host, int port) throws IOException {              return patch(delegate.createSocket(host, port));          }            @Override          public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {              return patch(delegate.createSocket(address, port, localAddress, localPort));          }            private Socket patch(Socket s) {              //代理SSLSocketFactory在创建一个Socket连接的时候,会设置Socket的可用的TLS版本。              if (s instanceof SSLSocket) {                  ((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);              }              return s;          }      }  }</code></pre>    <p>当然了,因为只是调整内部逻辑,所以这个工具的使用方法还是不变:</p>    <pre>  <code class="language-java">HttpsUtil.SSLParams sslParams = HttpsUtil.getSslSocketFactory(Utils.getContext(), new int[0], R.raw.xxxx, "password");      okHttpClient = new OkHttpClient.Builder()              .connectTimeout(10000L, TimeUnit.MILLISECONDS)              .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)              .hostnameVerifier(HttpsUtil.getHostnameVerifier())              .addInterceptor(new LoggerInterceptor(null, true))              .build();</code></pre>    <p> </p>    <p> </p>    <p>来自:http://blog.csdn.net/guiying712/article/details/56301736</p>    <p> </p>