问题描述
在企业应用开发场景中,跨系统接口对接是常见需求。现代企业往往部署多个异构系统,这些系统由不同供应商开发,采用不同的技术架构实现。系统间需要通过HTTP/HTTPS协议进行功能调用和数据交互。
HTTPS协议的安全机制可能带来开发挑战。当对接方的HTTPS证书存在配置问题时,标准的Java HTTP客户端会因证书验证失败而拒绝连接。
Java安全体系对HTTPS连接有严格的证书验证机制。客户端会检查服务器证书的颁发机构、有效期、域名匹配等信息。任何一项验证失败都会导致SSLHandshakeException异常。当对接方使用自签名证书或证书链不完整时,这种严格验证机制反而会成为系统集成的障碍。这种情况通常需要特殊处理,暂时绕过证书验证环节,以保障系统间的正常通信。
问题复现
未跳过验证的代码
public static String request(String requestUrl,
String requestMethod, String body, String contentType, Map<String, String> heads) throws Exception{
HttpsURLConnection connection;
StringBuilder buffer = new StringBuilder();
try {
URL url = new URL(requestUrl);
connection = (HttpsURLConnection) url.openConnection();
// 创建不验证主机名的 HostnameVerifier
connection.setHostnameVerifier((hostname, session) -> true);
// 设置请求头属性
if(heads != null){
for (Map.Entry<String, String> map : heads.entrySet()) {
String key = map.getKey();
String value = map.getValue();
connection.setRequestProperty(key, value);
}
}
// 设置请求方式
connection.setRequestMethod(requestMethod);
if (StrUtil.isNotEmpty(contentType)){
connection.setRequestProperty("content-type", contentType);
}
// 当有数据需要提交时
if (null != body) {
OutputStream outputStream = connection.getOutputStream();
// 设置编码格式,防止中文乱码
outputStream.write(body.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(
inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
connection.disconnect();
return buffer.toString();
} catch (Exception e) {
throw new Exception(e);
}
}
运行结果

调用上述方法后,运行出现了" javax.net.ssl.SSLHandshakeException"的错误,看这句错误 提示信息:Caused by: 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。这条错误提示信息意味着Java无法找到一条有效的证书链来验证请求的目标(通常是服务器)的证书。这通常是因为服务器的证书没有被一个Java信任的证书颁发机构(CA)签发,或者证书链中的某个证书不在Java的信任库中。
解决方案一
我们可以通过在调用HTTPS接口时跳过证书验证的方式处理上述问题。
具体代码
public static String requestIgnoreSSL(String requestUrl,
String requestMethod, String body, String contentType, Map<String, String> heads) throws Exception{
HttpsURLConnection connection;
StringBuilder buffer = new StringBuilder();
try {
// 创建一个信任所有证书的 TrustManager
TrustManager[] trustManagers = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// 安装信任所有证书的TrustManager
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, new java.security.SecureRandom());
URL url = new URL(requestUrl);
connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
// 创建不验证主机名的 HostnameVerifier
connection.setHostnameVerifier((hostname, session) -> true);
// 设置请求头属性
if(heads != null){
for (Map.Entry<String, String> map : heads.entrySet()) {
String key = map.getKey();
String value = map.getValue();
connection.setRequestProperty(key, value);
}
}
// 设置请求方式
connection.setRequestMethod(requestMethod);
if (StrUtil.isNotEmpty(contentType)){
connection.setRequestProperty("content-type", contentType);
}
// 当有数据需要提交时
if (null != body) {
OutputStream outputStream = connection.getOutputStream();
// 设置编码格式,防止中文乱码
outputStream.write(body.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(
inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
connection.disconnect();
return buffer.toString();
} catch (Exception e) {
throw new Exception(e);
}
}
说明:
-
自定义信任管理器(TrustManager):创建一个X509TrustManager,覆盖验证方法以信任所有证书。
- checkClientTrusted和checkServerTrusted方法留空,跳过证书验证。 - getAcceptedIssuers返回空数组,表示不限制可接受的CA。 -
配置SSLContext:使用自定义的TrustManager初始化SSLContext。
- 使用TLS协议初始化SSLContext。
- init方法的第一个参数为null表示不使用客户端证书,第二个参数传入信任所有证书的TrustManager数组。 -
设置默认的SSL套接字工厂和主机名验证器:替换HttpsURLConnection的默认设置,跳过主机名验证。
- 设置HostnameVerifier直接返回true,接受所有主机名。
解决方案二
直接使用Hutool工具提供的 HttpRequest 对象,它会默认跳过 SSL 验证。
具体代码
1、引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.24</version>
</dependency>
2、代码示例:
public static void main(String[] args) throws Exception {
// GET示例
System.out.println(HttpRequest.get("https://localhost:8080/testGet/").execute().body());
JSONObject jsonObject = new JSONObject();
jsonObject.put("username", "admin");
jsonObject.put("password", "123456");
// POST示例
System.out.println(HttpRequest.post("https://localhost:8080/login").body(jsonObject.toString()).execute().body());
}
总结
总的来说,如果程序需要跳过HTTPS证书验证,使用Hutool工具可以用最少最简单的代码达到我们的需求,如果项目中未使用hutool就需要修改我们的代码创建并安装一个信任所有证书的 TrustManager便可解决问题。
在Java应用程序中跳过HTTPS证书验证通常是不推荐的,因为这会削弱应用程序的安全性,使其容易受到中间人攻击。因此跳过HTTPS证书验证仅适用于受控的环境中。推荐使用有效证书,或通过正规CA签发自签名证书,并将证书导入Java信任库(cacerts)来解决问题。
1万+

被折叠的 条评论
为什么被折叠?



