Okhttp3 Https请求配置自签名证书

2019-11-16  本文已影响0人  王魔王

有关Https请求的原理本篇帖子不做过多赘述,今天要做的就是带领大家配置Https请求中自签名证书的问题

先贴一段OkHttp初始化的代码,这段代码地球人都知道

  okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                    .readTimeout(5, TimeUnit.SECONDS)
                    .connectTimeout(5,TimeUnit.SECONDS)
                    .writeTimeout(5,TimeUnit.SECONDS)
                `.hostnameVerifier()`//校验主机名,用于域名验证
                    `.sslSocketFactory()`//校验SSL证书
                    .build();

上面代码中,绿色部分的代码就是跟Https相关的代码了

先写一个简单的,即校验主机名的部分

这里需要一个HostnameVerifier对象,这个对象直接new出来即可

new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                          //hostname:本次请求的域名,如果域名是我们认可的域名,那么返回true,本次请求继续执行,返回false会中断本次请求
                         return    hostname.equals("xxxxxxxxx");
      //return true;如果不进行校验,直接return true,代表信任所有请求
                        }
                    }

那么代码就成了下面的样子👇

 okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                    .readTimeout(5, TimeUnit.SECONDS)
                    .connectTimeout(5,TimeUnit.SECONDS)
                    .writeTimeout(5,TimeUnit.SECONDS)
                .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                          //hostname:本次请求的域名,如果域名是我们认可的域名,那么返回true,本次请求继续执行,返回false会中断本次请求
                         return    hostname.equals("xxxxxxxxx");
      //return true;如果不进行校验,直接return true,代表信任所有请求
                        }
                    })`//校验主机名,用于域名验证
                    .sslSocketFactory()`//校验SSL证书
                    .build();

接下来是校验SSL证书部分的代码,这部分代码有点繁琐,大家照着步骤来即可

.sslSocketFactory()这里需要两个参数,一个是SSLSocketFactory,一个是X509TrustManager,大家先不要关注这两个对象的作用,只需要关注这两个对象的初始化步骤即可
1、将下载的证书放到assets目录中
然后复制下面的代码,用于生成证书对象

/**
     * 根据asset下证书的名字取出证书,然后变成流,在变成证书对象
     */
    private static X509Certificate readCert(Context context, String assetName) {
        InputStream inputStream = null;
        try {
            inputStream = context.getAssets().open(assetName);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        X509Certificate cert = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) cf.generateCertificate(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Throwable ex) {
            }
        }
        return cert;
    }

2、自定义MyTrustManager类,类结构如下

/**
     * 实现了 X509TrustManager
     * 通过此类中的 checkServerTrusted 方法来确认服务器证书是否正确
     */
    private static final class MyTrustManager implements X509TrustManager {
        X509Certificate cert;

        MyTrustManager(X509Certificate cert) {
            this.cert = cert;
        }

        @Override// 我们在客户端只做服务器端证书校验。
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        /**
         * @param chain 服务端返回的证书数组,因为服务器可能有多个https证书,我们在这里的
         *              逻辑就是拿到第一个证书,然后和本地证书判断,如果不一致,异常!!!
         */
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//             确认服务器端证书和代码中 hard code 的 CRT 证书相同。
//            这里因为我们服务器只有一个证书,没有遍历,如果有多个,这里是for循环取出挨个判断
            if (chain[0].equals(this.cert)) {
                return;
            }
            throw new CertificateException("checkServerTrusted No trusted server cert found!");
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }
    }

3、定义初始化SslSocketFactory的方法

//通过下载的证书文件、自定义的MyTrustManager对象来初始化SslSocketFactory
 private  void initSslSocketFactory(){
        try {
            sslContext = SSLContext.getInstance("TLS");
            //从assets文件夹下根据证书名字读取证书,变成一个可用的证书对象
            x509Certificate = readCert(App.context, CERTIFICATE_NAME);
            //校验服务端和本地证书是否一致
            mTrustManager = new MyTrustManager(x509Certificate);
            //初始化必要的对象,固定格式直接使用即可
            sslContext.init(null, new TrustManager[]{
                    mTrustManager
            }, new java.security.SecureRandom());
            mSslSocketFactory = sslContext.getSocketFactory();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

到此,有关SslSocketFactory的构建工作就完成了,我们只需要在OkHttp的初始化代码中使用有关对象即可
为了方便使用,我们应该把上面的代码都统一封装到一个工具类中
于是代码就成了现在的样子

/**
 * @Author:AbnerMing
 * @Description:
 * @Date:2019/11/7 8:53
 */
public class SSLSocketFactoryUtils {
    private static final String HOST_NAME = "xxxxxx";//请求服务器的主机名称
    private static String CERTIFICATE_NAME = "xxxx.crt";//下载的证书的文件名称
    //证书
    private X509Certificate x509Certificate;
    //需要配置给ok的SSLSocketFactory
    private SSLSocketFactory mSslSocketFactory;
    private SSLContext sslContext;

    //证书管理者
    private MyTrustManager mTrustManager;

    private static  SSLSocketFactoryUtils instance;

    private SSLSocketFactoryUtils() {
      initSslSocketFactory();
    }

    private  void initSslSocketFactory(){
        try {
            sslContext = SSLContext.getInstance("TLS");
            //从assets文件夹下根据证书名字读取证书,变成一个可用的证书对象
            x509Certificate = readCert(App.context, CERTIFICATE_NAME);
            //校验服务端和本地证书是否一致
            mTrustManager = new MyTrustManager(x509Certificate);
            //初始化必要的对象,固定格式直接使用即可
            sslContext.init(null, new TrustManager[]{
                    mTrustManager
            }, new java.security.SecureRandom());
            mSslSocketFactory = sslContext.getSocketFactory();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static SSLSocketFactoryUtils getInstance() {
        if (instance == null) {
            instance=new SSLSocketFactoryUtils();
        }
        return instance;
    }

    /**
     * 根据asset下证书的名字取出证书,然后变成流,在变成证书对象
     */
    private static X509Certificate readCert(Context context, String assetName) {
        InputStream inputStream = null;
        try {
            inputStream = context.getAssets().open(assetName);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        X509Certificate cert = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) cf.generateCertificate(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Throwable ex) {
            }
        }
        return cert;
    }

    public  HostnameVerifier getHostnameVerifier() {
        return hostnameVerifier;
    }

    public SSLSocketFactory getmSslSocketFactory() {
        return mSslSocketFactory;
    }

    public MyTrustManager getmTrustManager() {
        return mTrustManager;
    }

    /**
     * 实现了 X509TrustManager
     * 通过此类中的 checkServerTrusted 方法来确认服务器证书是否正确
     */
    private static final class MyTrustManager implements X509TrustManager {
        X509Certificate cert;

        MyTrustManager(X509Certificate cert) {
            this.cert = cert;
        }

        @Override// 我们在客户端只做服务器端证书校验。
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        /**
         * @param chain 服务端返回的证书数组,因为服务器可能有多个https证书,我们在这里的
         *              逻辑就是拿到第一个证书,然后和本地证书判断,如果不一致,异常!!!
         */
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//             确认服务器端证书和代码中 hard code 的 CRT 证书相同。
//            这里因为我们服务器只有一个证书,没有遍历,如果有多个,这里是for循环取出挨个判断
            if (chain[0].equals(this.cert)) {
                return;
            }
            throw new CertificateException("checkServerTrusted No trusted server cert found!");
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }
    }

    /**
     * 服务器域名验证,拿到请求接口的域名和本地配的域名进行比较,如果一样返回True
     */
    private  final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return hostname.equals(HOST_NAME);
          //return true;
        }
    };
}

最后使用

 okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                    .readTimeout(5, TimeUnit.SECONDS)
                    .connectTimeout(5,TimeUnit.SECONDS)
                    .writeTimeout(5,TimeUnit.SECONDS)
                    .hostnameVerifier(SSLSocketFactoryUtils.getInstance().getHostnameVerifier())
                    .sslSocketFactory(SSLSocketFactoryUtils.getInstance().getmSslSocketFactory(),
                            SSLSocketFactoryUtils.getInstance().getmTrustManager())
                    .build();
上一篇下一篇

猜你喜欢

热点阅读