共计 4765 个字符,预计需要花费 12 分钟才能阅读完成。
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 服务器利用会话密钥加密与客户端之间的通信。
2)java 程序的证书信任规则如上文所述,客户端会从服务端拿到证书信息。调用端(客户端)会有一个证书信任列表,拿到证书信息后,会判断该证书是否可信任。如果是用浏览器访问 https 资源,发现证书不可信任,一般会弹框告诉用户,对方的证书不可信任,是否继续之类。Java 虚拟机并不直接使用操作系统的 keyring,而是有自己的 security manager。与操作系统类似,jdk 的 security manager 默认有一堆的根证书信任。如果你的 https 站点证书是花钱申请的,被这些根证书所信任,那使用 java 来访问此 https 站点会非常方便。因此,如果用 java 访问 https 资源,发现证书不可信任,则会报文章开头说到的错误。
解决问题的方法 1)将证书导入到 jdk 的信任证书中(理论上应该可行,未验证)2)在客户端(调用端)添加逻辑,忽略证书信任问题第一种方法,需要在每台运行该 java 程序的机器上,都做导入操作,不方便部署,因此,采用第二种方法。下面贴下该方法对应的代码。验证可行的代码 1)先实现验证方法
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
System.out.println(“Warning: URL Host: ” + urlHostName + ” vs. ”
+ session.getPeerHost());
return true;
}
};
private static void trustAllHttpsCertificates() throws Exception {
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
javax.net.ssl.TrustManager tm = new miTM();
trustAllCerts[0] = tm;
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
.getInstance(“SSL”);
sc.init(null, trustAllCerts, null);
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
.getSocketFactory());
}
static class miTM implements javax.net.ssl.TrustManager,
javax.net.ssl.X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(
java.security.cert.X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(
java.security.cert.X509Certificate[] certs) {
return true;
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
}
2)在访问 https 资源前,调用
trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);
以下是一个具体的例子:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import org.apache.log4j.Logger;
import org.htmlparser.util.ParserException;
import com.xwtech.parser.GetRequestHtmlParser;
import com.xwtech.pojo.ExtendCandidate;
/*
* GET 请求类
*/
public class GetRequest {
private String url = “https://b2b.10086.cn/b2b/main/viewNoticeContent.html?noticeBean.id=”;
private Logger logger;
public GetRequest() {
logger = Logger.getLogger(GetRequest.class);
}
private static void trustAllHttpsCertificates() throws Exception {
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
javax.net.ssl.TrustManager tm = new miTM();
trustAllCerts[0] = tm;
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance(“SSL”);
sc.init(null, trustAllCerts, null);
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}
public void getData(String id) {
this.url = url + id;
BufferedReader in = null;
HttpURLConnection conn = null;
String result = “”;
try {
// 该部分必须在获取 connection 前调用
trustAllHttpsCertificates();
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
logger.info(“Warning: URL Host: ” + urlHostName + ” vs. ” + session.getPeerHost());
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
conn = (HttpURLConnection)new URL(url).openConnection();
// 发送 GET 请求必须设置如下两行
conn.setDoInput(true);
conn.setRequestMethod(“GET”);
// flush 输出流的缓冲
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
logger.error(“ 发送 GET 请求出现异常!\t 请求 ID:”+id+”\n”+e.getMessage()+”\n”);
} finally {// 使用 finally 块来关闭输出流、输入流
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
logger.error(“ 关闭数据流出错了!\n”+ex.getMessage()+”\n”);
}
}
// 获得相应结果 result, 可以直接处理 ……
}
static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
return true;
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
}
}