logo-cover-HttpClient 使用SSL访问HTTPS(提高篇)

1.概述

本文将展示如何使用“接受所有”SSL支持配置Apache HttpClient 4。目标很简单 - 使用没有有效证书的HTTPS URL。

 

2. SSLPeerUnverifiedException

在没有使用HttpClient配置SSL的情况下,以下测试(使用HTTPS URL)将失败:

public class HttpLiveTest {
 
    @Test(expected = SSLPeerUnverifiedException.class)
    public void whenHttpsUrlIsConsumed_thenException() 
      throws ClientProtocolException, IOException {
  
        DefaultHttpClient httpClient = new DefaultHttpClient();
        String urlOverHttps
          = "https://localhost:8080/spring-security-rest-basic-auth";
        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建立发生。
 

3.配置SSL - 全部接受(HttpClient <4.3)

现在让我们将HTTP客户端配置为信任所有证书链,而不管它们的有效性如何:

@Test
public void givenAcceptingAllCertificates_whenHttpsUrlIsConsumed_thenException() 
  throws IOException, GeneralSecurityException {
    TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
    SSLSocketFactory sf = new SSLSocketFactory(
      acceptingTrustStrategy, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("https", 8443, sf));
    ClientConnectionManager ccm = new PoolingClientConnectionManager(registry);
 
    DefaultHttpClient httpClient = new DefaultHttpClient(ccm);
 
    String urlOverHttps 
      = "https://localhost:8443/spring-security-rest-basic-auth/api/bars/1";
    HttpGet getMethod = new HttpGet(urlOverHttps);
     
    HttpResponse response = httpClient.execute(getMethod);
    assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
}

随着新的TrustStrategy现在覆盖标准证书验证过程(应该咨询配置的信任管理器) - 测试现在通过,客户端可以使用HTTPS URL
 

4. 带SSL 的Spring RestTemplate(HttpClient <4.3)

现在我们已经看到了如何配置一个支持SSL 的原始HttpClient,让我们来看看更高级别的客户端--Spring RestTemplate

未配置SSL,以下测试将按预期失败:

@Test(expected = ResourceAccessException.class)
public void whenHttpsUrlIsConsumed_thenException() {
    String urlOverHttps 
      = "https://localhost:8443/spring-security-rest-basic-auth/api/bars/1";
    ResponseEntity<String> response 
      = new RestTemplate().exchange(urlOverHttps, HttpMethod.GET, null, String.class);
    assertThat(response.getStatusCode().value(), equalTo(200));
}

那么让我们来配置SSL:
 

import static org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
 
...
@Test
public void givenAcceptingAllCertificates_whenHttpsUrlIsConsumed_thenException() 
  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/spring-security-rest-basic-auth/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支持配置请求工厂,然后实例化通过此预配置工厂的模板。
 

5.配置SSL - 全部接受(HttpClient 4.4)

在HttpClient 4.4版本中,现在不推荐使用SSLSocketFactory,我们可以简单地配置我们的HttpClient,如下所示:

@Test
public void givenIgnoringCertificates_whenHttpsUrlIsConsumed_thenCorrect()
  throws Exception {
    SSLContext sslContext = new SSLContextBuilder()
      .loadTrustMaterial(null, (certificate, authType) -> true).build();
 
    CloseableHttpClient client = HttpClients.custom()
      .setSSLContext(sslContext)
      .setSSLHostnameVerifier(new NoopHostnameVerifier())
      .build();
    HttpGet httpGet = new HttpGet(HOST_WITH_SSL);
    httpGet.setHeader("Accept", "application/xml");
 
    HttpResponse response = client.execute(httpGet);
    assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
}

6. 带SSL 的Spring RestTemplate(HttpClient 4.4)

我们可以用同样的方法来配置我们的RestTemplate

@Test
public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect() 
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));
}

7.总结

本教程讨论了如何为Apache HttpClient配置SSL,以便它能够使用任何HTTPS URL,而不考虑证书。还展示了Spring RestTemplate的相同配置。

然而,要理解的重要一点是,这种策略完全忽略了证书检查 - 这使得它不安全,只能在有意义的地方使用。