java程序在访问https资源时,出现报错


sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target


这本质上,是java在访问https资源时的证书信任问题。如何解决这个问题呢?




为何有这个问题?


解决这个问题前,要了解


1)https通信过程


客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。



(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。



(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。



(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。



(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。



(5)Web服务器利用自己的私钥解密出会话密钥。



(6)Web服务器利用会话密钥加密与客户端之间的通信。



java业务开发验证 java验证证书_服务器



2)java程序的证书信任规则



如上文所述,客户端会从服务端拿到证书信息。调用端(客户端)会有一个证书信任列表,拿到证书信息后,会判断该证书是否可信任。



如果是用浏览器访问https资源,发现证书不可信任,一般会弹框告诉用户,对方的证书不可信任,是否继续之类。



Java虚拟机并不直接使用操作系统的keyring,而是有自己的security manager。与操作系统类似,jdk的security manager默认有一堆的根证书信任。如果你的https站点证书是花钱申请的,被这些根证书所信任,那使用java来访问此https站点会非常方便。因此,如果用java访问https资源,发现证书不可信任,则会报文章开头说到的错误。




解决问题的方法


1)将证书导入到jdk的信任证书中(理论上应该可行,未验证)


2)在客户端(调用端)添加逻辑,忽略证书信任问题


第一种方法,需要在每台运行该java程序的机器上,都做导入操作,不方便部署,因此,采用第二种方法。下面贴下该方法对应的代码。




验证可行的代码(以httpclient为例)


SSLContext sslContext = null;
            try {
                sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
            } catch (Exception e) {
                //e.printStackTrace();
                log.error("fali custom SSL , ignoreCer fail");
            }
            SSLConnectionSocketFactory sslCSF = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            clientBuilder = clientBuilder.setSSLSocketFactory(sslCSF);