1. 概述
本文將演示如何配置 Apache HttpClient 4 & 5,並啓用“接受所有” SSL 支持。目標很簡單——消費無效證書的 HTTPS URL。
如果您想深入瞭解 HttpClient 的更多高級功能,請參閲 主 HttpClient 指南。
2. SSLPeerUnverifiedException
在沒有配置 HttpClient 的 SSL 協議的情況下,以下測試 – 消費 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 建立有效的信任鏈時,會發生 javax.net.ssl.SSLPeerUnverifiedException 異常。
3. 配置 SSL – 接受所有 (HttpClient 5)
現在,我們將配置 HTTP 客户端,使其無論證書鏈的有效性如何,都信任所有證書鏈:
@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. 使用 SSL 的 Spring RestTemplate (HttpClient 5)
現在我們已經瞭解瞭如何配置帶 SSL 支持的原始 HttpClient,現在讓我們來查看一個更高層次的客户端——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,然後實例化模板,並將此預配置的工廠傳遞給它。
2. 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 的配置方法。然而,需要注意的是 這種策略完全忽略了證書驗證——這使其不安全,僅適用於在證書驗證不影響的情況下使用。