遇到这么一个需求,我们是两套系统内网部署,A系统签了本地的ssl证书,B系统需要访问A系统的一个接口,在测试环境时,没有使用ssl,进行接口开发,没有问题,后面生产环境后,接口不通,报错“
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
”
这个“PKIX path building failed”,大概就是提示无法获取有效的证书,因此JAVA进行常规的POST请求时,没法实现该功能,常规的POST请求如下:
/**
* 发送POST请求
* @param url
* @param params
* @return
*/
public String sendPOSTRequest(String url, MultiValueMap<String, Object> params) {
RestTemplate client = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
HttpMethod method = HttpMethod.POST;
// 以表单的方式提交
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 将请求头部和参数合成一个请求
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers);
// 执行HTTP请求,将返回的结构使用String类格式化
ResponseEntity<String> response = client.exchange(url, method, requestEntity, String.class);
return response.getBody();
}
需要将请求修改为一下方式:需要实现证书信任管理器,MyX509TrustManager.java 和NullHostNameVerifier.java
MyX509TrustManager.java源码如下:
package com.dhproject.utils.ssl;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* 证书信任管理器(用于https请求)
*
*/
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
NullHostNameVerifier.java源码如下:
package com.dhproject.utils.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class NullHostNameVerifier implements HostnameVerifier{
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
通过证书信任管理器实现POST请求如下:
package com.dhproject.utils.ssl;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
public class SslPost {
/**
* 发送POST请求
* @param urlStr
* @param param
* @return
*/
public static String sendPOSTRequest(String urlStr,String param) {
try{
//设置可通过ip地址访问https请求
HttpsURLConnection.setDefaultHostnameVerifier(new NullHostNameVerifier());
TrustManager[] tm = {new MyX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(urlStr);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setSSLSocketFactory(ssf);
con.setRequestMethod("POST"); // 设置以POST方式提交数据
con.setDoInput(true); // 打开输入流,以便从服务器获取数据
con.setDoOutput(true);// 打开输出流,以便向服务器提交数据
//设置发送参数
PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutputStream(),"UTF-8"));
out.print(param);
out.flush();
out.close();
//读取请求返回值
InputStreamReader in = new InputStreamReader(con.getInputStream(),"UTF-8");
BufferedReader bfreader = new BufferedReader(in);
String result = "";
String line = "";
while ((line = bfreader.readLine()) != null) {
result += line;
}
return result;
}catch (Exception e){
return "";
}
}
}
调用代码如下:
/**
* 查询
* @param roomId
* @param orderByColumn
* @param isAsc
* @return
*/
@PostMapping("/list")
@ResponseBody
public Object list(Long roomId,String orderByColumn,String isAsc)
{
return SslPost.sendPOSTRequest("https://localhost/api/api","roomId="+(roomId==null?"":roomId.toString())+"&orderByColumn="+orderByColumn+"&isAsc="+isAsc);
}