1. 概述
本文将展示如何为 Apache HttpClient 4 & 5 配置“全接受”SSL 支持。目标很简单:消费没有有效证书的 HTTPS URL。
如果想深入了解 HttpClient 的其他高级用法,可以参考 **HttpClient 完整指南**。
2. SSLPeerUnverifiedException 异常
未配置 SSL 的 HttpClient 在访问 HTTPS URL 时会失败:
@Test
void whenHttpsUrlIsConsumed_thenException() {
String urlOverHttps = "https://localhost:8082/httpclient-simple";
HttpGet getMethod = new HttpGet(urlOverHttps);
assertThrows(SSLPeerUnverifiedException.class, () -> {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(getMethod, new CustomHttpClientResponseHandler());
assertThat(response.getCode(), equalTo(200));
});
}
具体报错如下:
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:126)
...
当无法为 URL 建立有效的信任链时,就会抛出 SSLPeerUnverifiedException
。
3. 配置 SSL 全接受模式(HttpClient 5)
现在配置 HttpClient 信任所有证书链,无论其有效性如何:
@Test
void givenAcceptingAllCertificates_whenHttpsUrlIsConsumed_thenOk() throws GeneralSecurityException, IOException {
final HttpGet getMethod = new HttpGet(HOST_WITH_SSL);
final TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
final SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
final SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
final Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
final BasicHttpClientConnectionManager connectionManager =
new BasicHttpClientConnectionManager(socketFactoryRegistry);
try( CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
CloseableHttpResponse response = (CloseableHttpResponse) httpClient
.execute(getMethod, new CustomHttpClientResponseHandler())) {
final int statusCode = response.getCode();
assertThat(statusCode, equalTo(HttpStatus.SC_OK));
}
}
通过新的 TrustStrategy
覆盖标准证书验证流程(原本应检查配置的信任管理器),测试现在通过,客户端可以成功消费 HTTPS URL。
4. 配置 SSL 全接受模式(HttpClient 4.5)
@Test
public final void givenAcceptingAllCertificates_whenHttpsUrlIsConsumed_thenOk()
throws GeneralSecurityException {
TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
BasicHttpClientConnectionManager connectionManager =
new BasicHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.setConnectionManager(connectionManager).build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
ResponseEntity<String> response = new RestTemplate(requestFactory)
.exchange(urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
}
5. Spring RestTemplate 集成 SSL(HttpClient 5)
了解了原生 HttpClient 的 SSL 配置后,我们来看更高级的客户端——Spring RestTemplate
。
未配置 SSL 时,测试会按预期失败:
@Test
void whenHttpsUrlIsConsumed_thenException() {
final String urlOverHttps = "https://localhost:8443/httpclient-simple/api/bars/1";
assertThrows(ResourceAccessException.class, () -> {
final ResponseEntity<String> response = new RestTemplate()
.exchange(urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
});
}
现在配置 SSL:
@Test
void givenAcceptingAllCertificates_whenHttpsUrlIsConsumed_thenOk() throws GeneralSecurityException {
final TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
final SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
final BasicHttpClientConnectionManager connectionManager =
new BasicHttpClientConnectionManager(socketFactoryRegistry);
final CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
final HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
final ResponseEntity<String> response = new RestTemplate(requestFactory)
.exchange(urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode()
.value(), equalTo(200));
}
可以看到,这与原生 HttpClient 的 SSL 配置非常相似——我们为请求工厂配置 SSL 支持,然后通过这个预配置的工厂实例化 RestTemplate
。
6. Spring RestTemplate 集成 SSL(HttpClient 4.5)
@Test
void givenAcceptingAllCertificates_whenUsingRestTemplate_thenCorrect() {
final CloseableHttpClient httpClient = HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
final HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
final ResponseEntity<String> response = new RestTemplate(requestFactory).exchange(urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
}
7. 总结
本文介绍了如何为 Apache HttpClient 配置 SSL,使其能够消费任何 HTTPS URL(无论证书有效性)。同时也展示了 Spring RestTemplate
的相同配置方案。
⚠️ 重要提醒:这种策略完全跳过了证书检查,存在安全风险,仅适用于明确需要忽略证书验证的场景。
完整示例代码可在 GitHub 项目 中找到。这是一个基于 Eclipse 的项目,可以直接导入运行。HttpClient 4 的示例位于 Apache HttpClient 4 模块。
关键要点
✅ 通过 TrustStrategy
覆盖默认证书验证
✅ 使用 NoopHostnameVerifier
禁用主机名验证
✅ HttpClient 4 和 5 的配置略有差异
❌ 生产环境慎用全接受模式(存在安全风险)