本文将展示如何使用“全部接受”SSL支持配置Apache HttpClient 4。目标很简单 - 使用没有有效证书的HTTPS URL。
如果不使用HttpClient配置SSL ,以下测试(使用HTTPS URL)将失败:
public class RestClientLiveManualTest { @Test(expected = SSLPeerUnverifiedException.class) public void test() throws ClientProtocolException, IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); String urlOverHttps ="https://localhost:8082/httpclient-simple)"; HttpGet getMethod = new HttpGet(urlOverHttps); HttpResponse response = httpClient.execute(getMethod); assertThat(response.getStatusLine().getStatusCode(), 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) ... 复制代码
该 javax.net.ssl.SSLPeerUnverifiedException
,该报错产生原因,当无法有效为URL建立信任链的时候。
现在让我们将HTTPClient配置为信任所有证书,无论其有效性如何:
@Test public final void test() throws GeneralSecurityException { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); CloseableHttpClient httpClient = (CloseableHttpClient) requestFactory.getHttpClient(); TrustStrategy acceptingTrustStrategy = (cert, authType) -> true; SSLSocketFactory sf = new SSLSocketFactory(acceptingTrustStrategy, ALLOW_ALL_HOSTNAME_VERIFIER); httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 8443, sf)); ResponseEntity<String> response = new RestTemplate(requestFactory). exchange(urlOverHttps, HttpMethod.GET, null, String.class); assertThat(response.getStatusCode().value(), equalTo(200)); } 复制代码
随着acceptingTrustStrategy 配置了 true
的测试通过,client能够消费的HTTPS URL。
使用新的HTTPClient,现在我们有了一个增强的,重新设计的默认SSL主机名验证程序。此外,通过引入SSLConnectionSocketFactory和RegistryBuilder,可以轻松构建SSLSocketFactory。所以我们可以编写上面的测试用例,如:
@Test public final void test() 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)); } 复制代码
现在我们已经了解了如何配置具有SSL支持的原始HttpClient,让我们来看看更高级别的方式-Spring RestTemplate。
如果未配置SSL,则以下测试将按预期会抛异常:
@Test(expected = ResourceAccessException.class) public void test() { String urlOverHttps = "https://localhost:8443/httpclient-simple/api/bars/1"; ResponseEntity<String> response = new RestTemplate().exchange(urlOverHttps, HttpMethod.GET, null, String.class); assertThat(response.getStatusCode().value(), equalTo(200)); } 复制代码
那么让我们配置SSL:
@Test public void test() throws GeneralSecurityException { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); DefaultHttpClient httpClient = (DefaultHttpClient) requestFactory.getHttpClient(); TrustStrategy acceptingTrustStrategy = (cert, authType) -> true SSLSocketFactory sf = new SSLSocketFactory( acceptingTrustStrategy, ALLOW_ALL_HOSTNAME_VERIFIER); httpClient.getConnectionManager().getSchemeRegistry() .register(new Scheme("https", 8443, sf)); String urlOverHttps ="https://localhost:8443/httpclient-simple/api/bars/1"; ResponseEntity<String> response = new RestTemplate(requestFactory). exchange(urlOverHttps, HttpMethod.GET, null, String.class); assertThat(response.getStatusCode().value(), equalTo(200)); } 复制代码
这与我们为原始HttpClient配置SSL的方式非常相似 - 我们使用SSL支持配置请求工厂,然后我们实例化通过此预配置工厂的模板。
我们可以使用相同的方式配置我们的RestTemplate:
@Test public void test() throws ClientProtocolException, IOException { CloseableHttpClient httpClient = HttpClients.custom() .setSSLHostnameVerifier(new NoopHostnameVerifier()) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); ResponseEntity<String> response = new RestTemplate(requestFactory).exchange( urlOverHttps, HttpMethod.GET, null, String.class); assertThat(response.getStatusCode().value(), equalTo(200)); } 复制代码
本教程讨论了如何为Apache HttpClient配置SSL,以便它能够使用任何HTTPS URL,而不管证书是什么。还说明了Spring RestTemplate的相同配置。
然而,一个重要的事情是,这种策略完全忽略了证书检查 - 这使得它不安全,只能在有意义的地方使用。