Android中HTTPS之一(三)具体操作(代码实现) - Go语言中文社区

Android中HTTPS之一(三)具体操作(代码实现)


一.背景

前两篇对Http和Https的原理进行了介绍,接下来是在Android代码中如何具体配置;同时再强调说明一下如果使用权威ca机构申请(购买)的证书客户端也要进行验证。

二.涉及的点

2.1 利用JDK中的keytool生成ssl证书

2.2 使用权威CA机构实现https的时候,只有服务端的秘钥证书,而app端没有公钥证书(Android系统会检验服务端合法性),但是还是要“手动”验证服务端合法性(公司请第三方检测机构检测后要求必须有手动校验过程),此时如何处理。

2.3 使用自签名证书的时候如何校验服务端合法性

2.4 各种网络请求框架中如何配置(Httpclient,HttpsUrlConnection,Okhttp,Glide,WebView)

三.利用JDK中的keytool生成ssl证书

至于使用keytool和openssl生成证书的区别这里不详细对比,以及相互的转换等。配置后台时候兼容性等,这里使用tomcat。

3.1首先,生成服务端证书存储库文件(证书库,ycb_server.jks)

打开CMD命令行工具,进入到JDK中keytool.exe所在目录(例如我的是C:Program Files (x86)Javajdk1.8.0_51bin)

使用keytool命令生成证书:keytool -genkey -alias ycb_server -keypass 123456 -keyalg RSA -keysize 1024 -validity 3650 -keystore E:/yuncaibianhttps/ycb_server.jks -storepass 123456;解释如下:

keytool 
-genkey 
-alias ycb_server 别名
-keypass 123456 密码
-keyalg RSA 加密方式
-keysize 1024 加密密钥长度(可以不加这一行,默认2048-validity 3650 证书有效期(单位:天)
-keystore E:/yuncaibianhttps/ycb_server.jks 证书存放的具体路径
-storepass 123456 查看jks信息的密码

按照提示的各种问题填写信息,点击回车后无提示,自行查看相应的文件夹中有无生成(这里注意提前新建要保存的目录文件夹如我的是E:/yuncaibianhttps,不然会报io异常);具体如下图

这里写图片描述

3.2其次,从服务端证书存储库文件中导出客户端需要的公钥证书。

keytool -export -alias ycb_server -file E:/yuncaibianhttps/ycb_server.cer -keystore E:/yuncaibianhttps/ycb_server.jks -storepass 123456

如下图所示:

这里写图片描述

通过上面的操作就产生了我们需要的服务端配置需要的ycb_server.jks,和客户端需要的ycb_server.cer。如下图所示:

这里写图片描述

3.3再有,从cer文件中导出公钥字符串(备用)

keytool -printcert -rfc -file E:/yuncaibianhttps/ycb_server.cer 

我的生成如下:

-----BEGIN CERTIFICATE-----
MIICUjCCAbugAwIBAgIEBt35sTANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJjbjEQMA4GA1UE
CBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEMMAoGA1UEChMDY25yMQwwCgYDVQQLEwNjbnIx
DTALBgNVBAMTBGdvbmcwHhcNMTgwMzA2MDcwMTQzWhcNMjgwMzAzMDcwMTQzWjBcMQswCQYDVQQG
EwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEMMAoGA1UEChMDY25yMQww
CgYDVQQLEwNjbnIxDTALBgNVBAMTBGdvbmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAI31
yMvNkmdFLyDr4uI35lMZwZf9lqt/q0MHpIaMP7j4evtVMqs9SnAJwEprOK35+z5xIgqpD+ioy5Xf
e1g2crM0r22qU229WOT4OfSk12bcobWw9Dr7Hqy5hjdXDtJ7hwg+c4mYE4WWZOH6REkR58c0LCoe
4Y1g0iZOpXwiTjF7AgMBAAGjITAfMB0GA1UdDgQWBBTDpVe72BYQOuvexvW+WpDt5XrpPDANBgkq
hkiG9w0BAQsFAAOBgQB0KhMRb7LhFB/8jWRa9owvSF9rEPmx8BzcsrMDQQEU+XV1dBhrr+YgADcl
wgEHZjCHair5rTId888bdSN+OXYIMat5jRyH2MW+5ybRQYCCijQ6jjyK1TV+/hDNZSYtEk9RW9vC
I7WMhdclqcbnzT6COg1cJHgrMhLlMz8bsoDZWQ==
-----END CERTIFICATE-----

在代码中使用时候稍作处理如下:

private String PUB_KEY  = "-----BEGIN CERTIFICATE-----n" +
            "MIICUjCCAbugAwIBAgIEBt35sTANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJjbjEQMA4GA1UEn" +
            "CBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEMMAoGA1UEChMDY25yMQwwCgYDVQQLEwNjbnIxn" +
            "DTALBgNVBAMTBGdvbmcwHhcNMTgwMzA2MDcwMTQzWhcNMjgwMzAzMDcwMTQzWjBcMQswCQYDVQQGn" +
            "EwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEMMAoGA1UEChMDY25yMQwwn" +
            "CgYDVQQLEwNjbnIxDTALBgNVBAMTBGdvbmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAI31n" +
            "yMvNkmdFLyDr4uI35lMZwZf9lqt/q0MHpIaMP7j4evtVMqs9SnAJwEprOK35+z5xIgqpD+ioy5Xfn" +
            "e1g2crM0r22qU229WOT4OfSk12bcobWw9Dr7Hqy5hjdXDtJ7hwg+c4mYE4WWZOH6REkR58c0LCoen" +
            "4Y1g0iZOpXwiTjF7AgMBAAGjITAfMB0GA1UdDgQWBBTDpVe72BYQOuvexvW+WpDt5XrpPDANBgkqn" +
            "hkiG9w0BAQsFAAOBgQB0KhMRb7LhFB/8jWRa9owvSF9rEPmx8BzcsrMDQQEU+XV1dBhrr+YgADcln" +
            "wgEHZjCHair5rTId888bdSN+OXYIMat5jRyH2MW+5ybRQYCCijQ6jjyK1TV+/hDNZSYtEk9RW9vCn" +
            "I7WMhdclqcbnzT6COg1cJHgrMhLlMz8bsoDZWQ==n" +
            "-----END CERTIFICATE-----";

如下图:

这里写图片描述

关于怎么配置服务端,这里不做赘述网上很多了自行百度。

四.代码中配置https

4.1使用CA机构证书的时候(Httpclient中)

下面是云采编app实际生产中,由于只有服务端有秘钥证书,客户端没有,如果想要手动校验那么只能采用下面的做法:自己获取服务端的公钥字符串,然后保存,然后供后面验证用(依据:我们测试用的服务器肯定是我们自己的服务器,公钥字符串肯定是没问题的)。

4.1.1 如下代码中打印出服务端返回的证书信息获取公钥字符串PUB_KEY ,如下代码:

下面是HttpClient实体类

private final DefaultHttpClient httpClient;

//构造一个支持https的HttpClient
public RdHttpClient() {
    BasicHttpParams httpParams = new BasicHttpParams();

    ...省略代码:http基本请求配置...

    SchemeRegistry schemeRegistry = new SchemeRegistry();

    ...省略代码:支持http...

    //配置自己的SSLSocketFactory
    SSLSocketFactory sf = null;
    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore
                .getDefaultType());
        trustStore.load(null, null);
        sf = new MySSLSocketFactory(trustStore);//自己的SSLSocketFactory
        sf.setHostnameVerifier(MySSLSocketFactory.STRICT_HOSTNAME_VERIFIER);//关键点:严格校验服务器域名
    } catch (Exception e) {
        e.printStackTrace();
    }

    //支持https,端口号为443
    schemeRegistry.register(new Scheme("https", sf, 443));
    ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(
            httpParams, schemeRegistry);
    httpClient = new DefaultHttpClient(cm, httpParams);

    ...省略代码...
}

下面是自己实现的SSLSocketFactory 类

//重写的SSLSocketFactory 
public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            //关键,打印服务端返回的证书中公钥字符串PUB_KEY
                RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();
                String encoded = new BigInteger(1, pubkey.getEncoded()).toString(16);
                Log.e("PUB_KEY",encoded);
                }
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

打印log如下(当然字符串不全)

03-07 11:24:28.672 12103-12256/com.cnr.broadcastCollect E/PUB_KEY: 30820122300d06092a864886f70d01010105000382010f003082010a0282010100c9292d7

4.1.2 获取PUB_KEY 之后写死在MySSLSocketFactory 中或别的地方。

4.1.3 使用刚才获取的PUB_KEY 进行服务端合法性校验,只需将上面的MySSLSocketFactory 中的重写方法中补充完整。校验代码如下:

private final String PUB_KEY = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100c9......"

public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                //1.对服务端返回的证书做判空处理
                if (chain == null) {
                    throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");
                }
                if (!(chain.length > 0)) {
                    throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");
                }

                //2.校验加密算法种类(通常所说的 ECDHE 密钥交换默认都是指 ECDHE_RSA,使用 ECDHE 生成 DH 算法所需的公私钥,然后使用 RSA 算法进行签名,最后再计算得出对称密钥。)
                if (!(null != authType && authType.equalsIgnoreCase("ECDHE_RSA"))) {
                    throw new CertificateException("checkServerTrusted: AuthType is not ECDHE_RSA");
                }

                for (X509Certificate cert:chain){
                    try {
                        //3.检查证书是否在有效期内
                        cert.checkValidity();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                //4.校验公钥字符串是否相同
                RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();
                String encoded = new BigInteger(1, pubkey.getEncoded()).toString(16);
                final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);
                if (!expected) {
                    throw new CertificateException("checkServerTrusted: Expected public key: " + PUB_KEY + ", got public key:" + encoded);
                }
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

通过上面的代码可以看出主要进行了如下校验:

1. 对服务端返回的证书做判空处理

2. 校验加密算法种类

3. 检查证书是否在有效期内

4. 校验公钥字符串是否相同

下面是通过Fiddler抓包工具得到的如下图:

这里写图片描述
这里写图片描述

题外话:上图是一个登陆接口,用的post请求;从中我们也可以知道即使使用了https,使用简单的抓包工具都能获取到数据信息,所以想要真正的保密的地方还是要加密处理的,无论是对称加密还是非对称加密。

4.2分析一下几个现有HostnameVerifier,在默认的SSLSocketFactory类中

public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER 
        = new AllowAllHostnameVerifier();

public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 
    = new BrowserCompatHostnameVerifier();

public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER 
    = new StrictHostnameVerifier();

这里只分析StrictHostnameVerifier,前面两个简单,只是重写方法的传参差异

4.2.1STRICT_HOSTNAME_VERIFIER 分析

如果按照4.1.1中RdHttpClient中sf.setHostnameVerifier(MySSLSocketFactory.STRICT_HOSTNAME_VERIFIER);这样的写法会出现一种现象就是:只有在app进行第一次网络请求的时候才进行服务器端合法性校验,后面的网络请求都不会进行校验(除非将app从后台杀死)。下面来看一下现象;

测试的方法是在MySSLSocketFactory的TrustManager回调函数checkServerTrusted中打log,如下面的代码:

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

    Log.e("checkServerTrusted","checkServerTrusted");

    ...省略代码...
}

log代码如下

03-07 14:27:57.446 11056-11705/com.cnr.broadcastCollect E/HttpClient?getInstance: HttpClient getInstance
03-07 14:27:57.576 11056-11705/com.cnr.broadcastCollect E/checkServerTrusted: checkServerTrusted
03-07 14:27:57.714 11056-11705/com.cnr.broadcastCollect E/tag: js {"result":{"sex":"","channelName":"中国之声","channelId":"","dutyTitle":"","roleName":"节目编辑","userRole":"","departmentName":"早间节目部","departmentType":"","id":"","authority":"11111111111111111111111111111","token":"","roleType":"3","userName":"","departmentId":"","userPhonePath":"","loginName":""},"error":{"message":"","code":"0"}}
03-07 14:28:04.778 11056-11705/com.cnr.broadcastCollect E/HttpClient?getInstance: HttpClient getInstance
03-07 14:28:04.994 11056-11705/com.cnr.broadcastCollect E/tag: js  {"totalNum":1499,"result":[{"taskId":"",ck":"","}]
03-07 14:28:08.377 11056-11705/com.cnr.broadcastCollect E/HttpClient?getInstance: HttpClient getInstance
03-07 14:28:09.717 11056-11705/com.cnr.broadcastCollect E/tag: js  {"totalNum":453,"result":[{"taskId":"",,"createDate":"2018-03-07 10:10:29"}]

我们发现只有在登陆的时候(第一次请求)的时候验证了,而后面未验证(未调用checkServerTrusted)方法。

4.2.2从源码中找一下原因

StrictHostnameVerifier 类如下:

/**
下面注释再说名使用这种HostnameVerifier,就只在第一次链接的时候检查服务端的合法性,即造成上面的现象
 * <p/>
 * The hostname must match either the first CN, or any of the subject-alts.
 * A wildcard can occur in the CN, and in any of the subject-alts.  The
 * one divergence from IE6 is how we only check the first CN.  IE6 allows
 * a match against any of the CNs present.  We decided to follow in
 * Sun Java 1.4's footsteps and only check the first CN.  (If you need
 * to check all the CN's, feel free to write your own implementation!).
 * <p/>
 * 下面的注释说明严格控制域名匹配,和BrowserCompatHostnameVerifier形成对比,后者宽泛:允许子域名匹配也可以认为域名相同,确认合法
 * A wildcard such as "*.foo.com" matches only subdomains in the same
 * level, for example "a.foo.com".  It does not match deeper subdomains
 * such as "a.b.foo.com".
 */
@Deprecated
public class StrictHostnameVerifier extends AbstractVerifier {

    public final void verify(
            final String host, 
            final String[] cns,
            final String[] subjectAlts) throws SSLException {
        verify(host, cns, subjectAlts, true);
    }

    @Override
    public final String toString() { 
        return "STRICT"; 
    }

}

AbstractVerifier 类

public abstract class AbstractVerifier implements X509HostnameVerifier {
    public final void verify(final String host, final String[] cns,
                             final String[] subjectAlts,
                             final boolean strictWithSubDomains)
          throws SSLException {

        //下面注释解释了为何只在第一次链接时候校验,并且一些浏览器也是这么做的
        // Build the list of names we're going to check.  Our DEFAULT and
        // STRICT implementations of the HostnameVerifier only use the
        // first CN provided.  All other CNs are ignored.
        // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).

        //取出服务器返回的证书中的域名
        LinkedList<String> names = new LinkedList<String>();
        if(cns != null && cns.length > 0 && cns[0] != null) {
            names.add(cns[0]);
        }
        if(subjectAlts != null) {
            for (String subjectAlt : subjectAlts) {
                if (subjectAlt != null) {
                    names.add(subjectAlt);
                }
            }
        }

        //返回域名不能为空,否则抛异常
        if(names.isEmpty()) {
            String msg = "Certificate for <" + host + "> doesn't contain CN or DNS subjectAlt";
            throw new SSLException(msg);
        }

        // StringBuffer for building the error message.
        StringBuffer buf = new StringBuffer();

        // We're can be case-insensitive when comparing the host we used to
        // establish the socket to the hostname in the certificate.
        //本地请求服务域名整理为小写
        String hostName = host.trim().toLowerCase(Locale.ENGLISH);
        boolean match = false;
        //下面是匹配过程
        for(Iterator<String> it = names.iterator(); it.hasNext();) {
            // Don't trim the CN, though!
            String cn = it.next();
            cn = cn.toLowerCase(Locale.ENGLISH);
            // Store CN in StringBuffer in case we need to report an error.
            buf.append(" <");
            buf.append(cn);
            buf.append('>');
            if(it.hasNext()) {
                buf.append(" OR");
            }

            // The CN better have at least two dots if it wants wildcard
            // action.  It also can't be [*.co.uk] or [*.co.jp] or
            // [*.org.uk], etc...
            boolean doWildcard = cn.startsWith("*.") &&
                                 cn.indexOf('.', 2) != -1 &&
                                 acceptableCountryWildcard(cn) &&
                                 !isIPv4Address(host);

            if(doWildcard) {
                match = hostName.endsWith(cn.substring(1));
                //strictWithSubDomains决定对子域名的检查严格程度
                if(match && strictWithSubDomains) {
                    // If we're in strict mode, then [*.foo.com] is not
                    // allowed to match [a.b.foo.com]
                    match = countDots(hostName) == countDots(cn);
                }
            } else {
                match = hostName.equals(cn);
            }
            if(match) {
                break;
            }
        }
        if(!match) {
            throw new SSLException("hostname in certificate didn't match: <" + host + "> !=" + buf);
        }
    }
}

从上面可以看出如果有需要想每次请求都验证服务端合法性那么只能直接实现X509HostnameVerifier接口自己处理验证过程。

4.3使用自签名证书配置客户端

大体思路有两种:第一种自己实现SSLSocketFactory 关键是重写校验证书链TrustManager中的方法checkServerTrusted,自己手工检验服务端合法性;第二种利用预留在客户端的公钥文件生成证书添加到系统的被信任列表中,让系统按照默认的校验方式校验。(个人推荐第一种)

在“三”中已经生成了服务端和客户端的相应证书库和证书,这里只说Android端的配置,服务端的配置自行百度。由于4.1中没有公钥证书,所以采取了上述的折中方法;

4.3.1先写第一种方案:自己手工校验:

将公钥证书放在工程的assets文件夹中或者用3.3中取出的公钥字符串都是可以的。

RdHttpclient不用动,MySSLSocketFactory中代码稍作改动如下:

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                Log.e("checkServerTrusted","checkServerTrusted");

                //对服务端返回的证书做判空处理
                if (chain == null) {
                    throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");
                }
                if (!(chain.length > 0)) {
                    throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");
                }

                //校验加密算法种类(通常所说的 ECDHE 密钥交换默认都是指 ECDHE_RSA,使用 ECDHE 生成 DH 算法所需的公私钥,然后使用 RSA 算法进行签名,最后再计算得出对称密钥。)
                if (!(null != authType && authType.equalsIgnoreCase("ECDHE_RSA"))) {
                    throw new CertificateException("checkServerTrusted: AuthType is not ECDHE_RSA");
                }

                for (X509Certificate cert:chain){
                    try {
                        //检查证书是否在有效期内
                        cert.checkValidity();

                        //下面是和上面4.1.3中唯一的差异,增加了这一句,并将下面原有的验证公钥字符串相同的部分去掉。
                        //校验服务端返回的证书和本地预留的证书的一致性
                        cert.verify(getCert().getPublicKey());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                }
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }

    //下面两种获取证书的方法选一种即可
    //将公钥文件放在工程的assets文件夹中的时候,获取证书
    private X509Certificate getCert(){
        X509Certificate serverCert = null;
        try {
            InputStream certificate = App.getInstance().getResources().getAssets().open("ycb_server.cer");
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            serverCert = (X509Certificate) certificateFactory.generateCertificate(certificate);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return serverCert;
    }

    //将公钥信息以字符串的形式硬编码在类中的时候,获取证书
    private X509Certificate getCertFromString(){
        X509Certificate serverCert = null;
        try {
            InputStream certificate = new ByteArrayInputStream(PUB_KEY_CERT.getBytes());
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            serverCert = (X509Certificate) certificateFactory.generateCertificate(certificate);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return serverCert;
    }

    //下面的公钥字符串也是改过的
    private final String PUB_KEY_CERT  = "-----BEGIN CERTIFICATE-----n" +
            "MIICUjCCAbugAwIBAgIEBt35sTANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJjbjEQMA4GA1UEn" +
            "CBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEMMAoGA1UEChMDY25yMQwwCgYDVQQLEwNjbnIxn" +
            "HHHHBgNVBAMTBGdvbmcwHhcNMTgwMzA2MDcwMTQzWhcNMjgwMzAzMDcwMTQzWjBcMQswCQYDVQQGn" +
            "HHHHbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEKKKKYmVpamluZzEMMAoGA1UEChMDY25yMQwwn" +
            "HHHHVQQLEwNjbnIxDTALBgNVBAMTBGdvbmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAI31n" +
            "yMvNkmdFLyDr4uI35lMZwZf9lqt/q0MHpIaMP7j4evtVMqs9SnAJwEprOK35+z5xIgqpD+ioy5Xfn" +
            "e1g2crM0r22qU229WOT4OfSk12bcobWw9Dr7Hqy5KKKKDtJ7hwg+c4mYE4WWZOH6REkR58c0LCoen" +
            "4Y1g0iZOpXwiTjF7AgMBAAGjITAfMB0GA1UdDgQWBBTDpVe72BYQOuvexvW+WpDt5XrpPDANBgkqn" +
            "hkiG9w0BAQsFAAOBgQB0KhMRb7LhFB/8jWRa9owvSF9rEPmx8BzcsrMDQQEU+XV1dBhrr+YgADcln" +
            "wgEHZjCHair5rTId888bdSN+OXYIMat5jRyH2MW+5ybRQYCCijQ6jjyK1TV+/hDNZSYtEk9RW9vCn" +
            "I7WMhdclqcbnzT6COg1cJHgrMhLlMz8bsoDZWQ==n" +
            "-----END CERTIFICATE-----";
}

同时还可以采用自己验证域名的方式,如下面的代码;但是我不推荐这种做法,因为采用自带的严格检验的StrictHostnameVerifier 里面写的更加的全面一些。

sf.setHostnameVerifier(new AbstractVerifier() {
    @Override
    public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
        //校验过程
    }
});

4.3.2 第二种利用预留在客户端的公钥文件生成证书添加到系统的被信任列表中,让系统按照默认的校验方式校验。

这里偷懒请查看使用HttpsURLConnection,里面有一部分讲如何配置;

同时okhttp的配置请看洪洋大神的okhttp中配置https,讲的很详细,是在不行有全套的代码。

五.总结

从网上找东西,信息本来就多,找到了还不能用这是最让人闹心的。以后记录的东西一定是我自己项目中用过的或者真正实践过的,因为知道原理和真的用到生产中还是有不小差距的。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/gongxiaoou/article/details/79487750
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-16 12:52:22
  • 阅读 ( 1017 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢