1. 概述
在这个教程中,我们将学习如何使用Java HttpClient连接HTTPS URL,并处理那些没有有效SSL证书的URL。在早期的Java版本中,我们更倾向于使用Apache HTTPClient或OkHttp库。然而,在Java 11中,JDK增加了改进的HttpClient库。
让我们一起探索如何使用它来通过SSL调用服务。
2. 使用Java HttpClient调用HTTPS URL
我们将通过示例代码运行客户端。为了测试目的,我们将使用一个已存在的HTTPS运行的URL。
首先编写代码设置客户端并调用服务:
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.google.com/"))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
这里,我们首先使用HttpClient.newHttpClient()
方法创建了一个客户端,然后创建了一个请求并设置了我们将要访问的服务URL。最后,我们使用HttpClient.send()
方法发送请求,并收集响应——一个包含响应体的HttpResponse
对象。
当我们在测试用例中放置上述代码并执行以下断言时,我们会看到它通过了:
assertEquals(200, response.statusCode());
3. 调用无效的HTTPS URL
现在,我们将URL更改为一个没有有效SSL证书的另一个URL。我们可以通过更改请求对象来实现:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://wrong.host.badssl.com/"))
.build();
当我们再次运行测试时,会得到以下错误:
Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching wrong.host.badssl.com found.
at java.base/sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:212)
at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:103)
这是因为URL没有有效的SSL证书。
4. 避免SSL证书验证
4.1. 使用模拟信任管理器
为了跳过HttpClient自动执行的验证,我们可以扩展默认的 X509ExtendedTrustManager
对象,它是负责检查SSL证书有效性的类,通过重写默认的业务逻辑,使其成为空方法:
private static final TrustManager MOCK_TRUST_MANAGER = new X509ExtendedTrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
// empty method
}
// ... Other void methods
}
接着,我们需要将新的信任管理器实现提供给我们的HttpClient实例。为此,我们首先创建一个SSLContext
的HttpClient
实例的新实例,然后初始化对象,传入一个空的KeyManager
列表,一个新创建的信任管理器数组,以及SecureRandom
类的一个新实例(随机数生成器):
SSLContext sslContext = SSLContext.getInstance("SSL"); // OR TLS
sslContext.init(null, new TrustManager[]{DUMMY_TRUST_MANAGER}, new SecureRandom());
现在,我们可以使用这个更新后的SSLContext
构建我们的HttpClient:
HttpClient httpClient = HttpClient.newBuilder().sslContext(sslContext).build();
现在我们可以调用任何没有有效证书的URL,仍然可以得到成功响应:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://wrong.host.badssl.com/"))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Assertions.assertEquals(200, response.statusCode())
在下一段,我们将看到另一种方法,通过JVM标志来实现相同的结果,这将禁用每个HttpClient请求的证书验证。
4.2. 使用JVM禁用主机验证标志
作为解决上述问题的最后一种方法,我们将探讨一种绕过SSL证书验证的解决方案。
在Apache HttpClient中,我们可以修改客户端以绕过证书验证。然而,对于Java HttpClient,我们无法做到这一点。我们必须依赖于对JVM进行更改,以禁用主机验证。
一种方法是将网站的证书导入到Java Keystore。如果内部信任的网站数量较少,这是一个常见的做法。
但如果需要管理的网站数量众多或环境众多,这可能会变得繁琐。在这种情况下,我们可以使用属性jdk.internal.httpclient.disableHostnameVerification
来禁用主机验证。
我们可以在运行应用程序时通过命令行参数设置这个属性:
java -Djdk.internal.httpclient.disableHostnameVerification=true -jar target/java-httpclient-ssl-0.0.1-SNAPSHOT.jar
另一种选择是 在创建客户端之前,程序性地设置这个属性:
Properties props = System.getProperties();
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
HttpClient httpClient = HttpClient.newHttpClient();
现在,当我们运行测试时,会看到它通过了。
需要注意的是,改变这个属性意味着所有请求的证书验证都将被禁用。 这可能在生产环境中并不理想。但在非生产环境中,这是常见的做法。
5. 我们能使用Java HttpClient与Spring一起吗?
Spring提供了两个流行的HTTP请求接口:
-
RestTemplate
用于同步请求 -
WebClient
用于同步和异步请求
它们都可以与Apache HttpClient、OkHttp和旧的HttpURLConnection
等流行HTTP客户端一起使用。然而,我们不能将Java HttpClient集成到这两个接口中。它更像是它们的替代方案。
我们可以直接使用Java HttpClient进行同步和异步请求,转换请求和响应,添加超时等。因此,不需要Spring的接口即可使用。
6. 总结
在这篇文章中,我们探讨了如何使用Java HTTP Client连接需要SSL的服务器,并处理没有有效证书的URL。代码示例始终可在GitHub上找到。