https服务端验证客户端证书的问题?PKIX:unable to find valid certification 或Received fatal alert: handshake_failure

    相信大家都已经被PKIX:unable to find valid certification path to requested target这类问题搞的很烦了,ssl握手咋也不成功,难不成握手的姿势不对?网上也有很多解决办法,大多数的代码都是选择绕过,为啥有些人绕过可以,有些人绕过就不行呢?原因很简单,当服务端选择强制校验客户端证书的时候,你是绕不过去的,你只能选择信任别人,但是你不能让别人都信任你,对不对?所以你构建httpclient客户端时,一定要带上证书,这里以测试环境银联的pfx证书为例。Talk is cheap,show me the code.

    第一步,构建KeyStore对象,当然是通过pfx文件来构建啦,例如我这里的pfx文件为:CUPTest1.pfx,path当然就是这个文件所在的绝对路径了,这里补充一点,load方法中的1其实是pfx的证书密码,这个密码不会出现在pfx文件本身中,想想也不可能,这个密码是哪里来的呢,是银联告诉我的,一般pfx证书的说明文档中或说明xml文件中,会有明确的字段告诉你pfx证书的密码是多少。

    KeyStore keyStore = getKeyStoreByPfx("D://CUPTest1.pfx","1");

    public static KeyStore getKeyStoreByPfx(String path,String password){
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(new FileInputStream(new File(path)), password.toCharArray());
            return keyStore;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    第二步,选择信任任何人,构建TrustManager对象。这里的代码大家应该看的够多了,意义就一句话,不管是谁我都信了。

    TrustManager tm=getTrustManager();

    public static TrustManager getTrustManager(){
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
    }
 

    第三步,重点来了,我要敲黑板了!!!很多小伙伴都是因为接下来这一步没有正确初始化KeyManager导致握手失败。补充说明,这里pfx证书的密码1又一次用到了。不知道1是啥的,回去看下第一步。这一步其实是PKIX:unable to find valid certification path to requested target的元凶,不要和我说我把pfx证书转成cer证书在jdk的keystore中安装一下就可以了,我试过了,不行!为了防止有人说我没安装成功,这里上个图:

        KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmfactory.init(keyStore,"1".toCharArray());
        KeyManager[] keymanagers = kmfactory.getKeyManagers();

    第四步,构建SSLContext对象,不管什么代码实现,想要对接https,这个对象是不可或缺的,大家都懂的。当然除了TLSv1之外,还有TLSv1.1等等,这里就不再赘述了。选择一个服务端能兼容的最低版本即可。

SSLContext sslCtx = SSLContext.getInstance("TLSv1");

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

    第五步,构建SSLConnectionSocketFactory对象,这个对象就是构建各种httpclient的最终对象了,直接上代码。补充说明一点,

NoopHostnameVerifier.INSTANCE这玩意儿是啥,这里我解释一下,大家一定在调试过程中遇到这种错:

Certificate for <xx.xx.xx.xx> doesn't match any of the subject alternative names: [chargeback.chinaunionpay.net]

也就是IP和域名不匹配,而NoopHostnameVerifier.INSTANCE就是帮我们客户端跳过这类域名验证的策略。

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslCtx, NoopHostnameVerifier.INSTANCE);

最后一步,构建HttpClient对象就是水到渠成的事情了:

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

有了这个httpclient,你就可以为所欲为的访问对端的https服务啦!

下面就是实际请求的代码了:

        HttpPost httppost = new HttpPost("https://182.180.197.49:8099/internet/BOCMExpSvcProxy");
        String content="我是Post请求的内容";
        StringEntity se = new StringEntity(content, Charset.forName("UTF-8"));
        httppost.setEntity(se);
        CloseableHttpResponse response = httpclient.execute(httppost);
        HttpEntity entity = response.getEntity();
        System.out.println(response.getStatusLine());
        System.err.println(EntityUtils.toString(entity));

        补充说明一点:

        我通过下面的方式来加载客户端证书,实际上测试下来也是不行的,所以KeyManager是必须的!

SSLContexts.custom().loadTrustMaterial(new TrustSelfSignedStrategy()).loadKeyMaterial(keyStore, "1".toCharArray())
                .build();

       最后补充一点,这个配置挺有用的,可以看到完整的ssl handshake过程,如果你的https请求有问题,加了-Djavax.net.debug=all之后,一定可以在控制台看出错误的端倪。

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值