Apache HttpClient 4和5访问没有有效证书的HTTPS

发布于:2024-12-06 ⋅ 阅读:(120) ⋅ 点赞:(0)

本文将展示如何配置Apache HttpClient 4和5以支持“接受所有”SSL。

目标很简单——访问没有有效证书的HTTPS URL。

SSLPeerUnverifiedException

在未配置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异常。

配置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。

配置SSL - 接受所有(HttpClient 4.5)

对于HttpClient 4.5版本,配置方式类似,但使用了一些不同的API:

@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));
}

Spring RestTemplate与SSL(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支持的请求工厂配置了RestTemplate

结论

本教程讨论了如何配置Apache HttpClient以使其能够消费任何HTTPS URL,无论证书的有效性如何。

同样也展示了如何对Spring RestTemplate进行同样的配置。

重要的是要理解这种策略完全忽略了证书检查——这使得它不安全,仅应在合理的情况下使用。


网站公告

今日签到

点亮在社区的每一天
去签到