Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

This article will show how to configure the Apache HttpClient 4 with “Accept All” SSL support. The goal is simple – consume HTTPS URLs which do not have valid certificates.

If you want to dig deeper and learn other cool things you can do with the HttpClient – head on over to the main HttpClient guide.

Further reading:

HttpClient Connection Management

How to open, manage and close connections with the Apache HttpClient 4.

Advanced HttpClient Configuration

HttpClient configurations for advanced use cases.

HttpClient 4 – Send Custom Cookie

How to send Custom Cookies with the Apache HttpClient 4.

2. The SSLPeerUnverifiedException

Without configuring SSL with the HttpClient, the following test – consuming an HTTPS URL – will fail:

public class RestClientLiveManualTest {

    @Test(expected = SSLPeerUnverifiedException.class)
    public void whenHttpsUrlIsConsumed_thenException() 
      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));
    }
}

The exact failure is:

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)
    ...

The javax.net.ssl.SSLPeerUnverifiedException exception occurs whenever a valid chain of trust couldn't be established for the URL.

3. Configure SSL – Accept All (HttpClient < 4.3)

Let's now configure the HTTP client to trust all certificate chains regardless of their validity:

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

With the new TrustStrategy now overriding the standard certificate verification process (which should consult a configured trust manager) – the test now passes and the client is able to consume the HTTPS URL.

4. Configure SSL – Accept All (HttpClient 4.4 and Above)

With the new HTTPClient, now we have an enhanced, redesigned default SSL hostname verifier. Also with the introduction of SSLConnectionSocketFactory and RegistryBuilder, it's easy to build SSLSocketFactory. So we can write the above test case like :

@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. The Spring RestTemplate with SSL (HttpClient < 4.3)

Now that we have seen how to configure a raw HttpClient with SSL support, let's take a look at a higher level client – the Spring RestTemplate.

With no SSL configured, the following test fails as expected:

@Test(expected = ResourceAccessException.class)
public void whenHttpsUrlIsConsumed_thenException() {
    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));
}

So let's configure SSL:

@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/httpclient-simple/api/bars/1";
    ResponseEntity<String> response = new RestTemplate(requestFactory).
      exchange(urlOverHttps, HttpMethod.GET, null, String.class);
    assertThat(response.getStatusCode().value(), equalTo(200));
}

As you can see, this is very similar to the way we configured SSL for the raw HttpClient – we configure the request factory with SSL support and then we instantiate the template passing this preconfigured factory.

6. The Spring RestTemplate with SSL (HttpClient 4.4)

And we can use the same way to configure our 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. Conclusion

This tutorial discussed how to configure SSL for an Apache HttpClient so that it is able to consume any HTTPS URL, regardless of the certificate. The same configuration for the Spring RestTemplate is also illustrated.

An important thing to understand however is that this strategy entirely ignores certificate checking – which makes it insecure and only to be used where that makes sense.

The implementation of these examples can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
18 Comments
Oldest
Newest
Inline Feedbacks
View all comments
I. N. Secure
I. N. Secure
7 years ago

You should at least mention that your TrustStrategy implementation does not perform any verification of the used certificate at all, which renders the use of ssl pretty much useless, as a man in the middle can present any certificate matching the name, no matter who it was issued or signed by.

Eugen Paraschiv
7 years ago
Reply to  I. N. Secure

The article does mention repeatedly – in the very beginning (The goal is simple – consume HTTPS URLs which do not have valid certificates.) then in the Accept All chapter (Let’s now configure the http client to trust all certificate chains regardless of their validity…) and once more in the Conclusion, so hopefully it should be clear that the solution is simply ignoring certificates. The goal here is to be tolerant of invalid certificate chains when analyzing large datasets which may contain such URLs.
Thanks.
Eugen.

Mohan
Mohan
4 years ago

Hi Eugen,

Thanks for the wonderful article, Could you please share me the exact jar version details for this example. Since while using am getting into lot of jar conflict issues.(3.Configure SSL – Accept All (HttpClient < 4.3)).

Eugen Paraschiv
4 years ago
Reply to  Mohan

Hey Mohan – you can check out the exact versions of the libraries used in the article by having a look at the actual project code – over on github.
Hope that helps. Cheers,
Eugen.

znogger
znogger
7 years ago
Reply to  I. N. Secure

True, but a SSL connection provides two benefits: encryption and verification of the endpoint. If you trust any certificate then you loose the latter benefit. The connection is still encrypted and at least a man in the middle would have KNOW that the connector accepts any certificate to do something nasty. The barrier for an attacker has been lowered significantly but there are still advantages left. It still has a security level higher than a non-SSL connection.

Eugen Paraschiv
7 years ago
Reply to  znogger

Agreed – that particular connection has a lot more entry-points for a potential attacker. However, these connections are throw away – attacking such a connection would lead to nothing. It’s only when that’s the case that one should ignore certificates. One such usecase I had – in fact, the usecase that prompted me to look into this solution – was processing URLs to extract some data from the page (title, description).

Santhosh Baby
Santhosh Baby
4 years ago

Need a help
Im trying to authenticate ssl for https request .. based on your code for httpclient version 4.5 it worked only if my request uses https://localhost:443 (default port 443) .
my question is how to give the custom port tried searching every where didnt find a solution. please provide me help for httpclient version 4.5.
I tried using https://localhost:9002 directly but still it gets remapped to .443 port.. how do i solve this

Eugen Paraschiv
4 years ago
Reply to  Santhosh Baby

Hey Santhosh – before 4.3, there was a clear way to specify the port by registering a Scheme – and I’m sure that was replaced by an equality flexible API, but I haven’t looked at what that is recently.
You can start from the example in that test (in the Github project) and then follow the JavaDocs to see exactly how to replace that Scheme. Cheers,
Eugen.

Jim Bowring
Jim Bowring
4 years ago

Hi –

With this code:

CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();

HttpGet httpGet = new HttpGet(“https://sesar3.geoinfogeochem.org/sample/igsn/ODP000002”);
httpGet.setHeader(“accept:”, “application/xml”);

Then this call fails with the error listed below it (actually called inside try – elided for simplicity)
httpResponse = httpClient.execute(httpGet);

httpClient.execute(httpGet) = >Exception occurred in target VM: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target<

What's up?

ps: this works:
curl -X GET -H "accept: application/xml" "https://sesar3.geoinfogeochem.org/sample/igsn/ODP000002&quot; -L -k

Thanks.

Eugen Paraschiv
4 years ago
Reply to  Jim Bowring

I was able to reproduce it and it’s definitely odd – looking into it. Cheers,
Eugen.

Стас
Стас
4 years ago

We encountered the same issue. Any updates on this?

Grzegorz Piwowarek
Grzegorz Piwowarek
4 years ago
Reply to  Стас

SSLContext was missing. Article updated

Mike Smithson
Mike Smithson
4 years ago

I still am getting an unknownhostexception when implementing the code above using HTTPClient 4.5.1

Eugen Paraschiv
4 years ago
Reply to  Mike Smithson

Hey Mike – that’s interesting – is there a test/code sample you can point to so that I can quickly reproduce the problem? If that’s long and cannot be pasted here, go ahead and raise an issue over on Github and I’ll have a look. Cheers,
Eugen.

Pawan H
Pawan H
4 years ago

Hi. This is a useful article and the site as well in general. My question is slightly different (perhaps unrelated), what if I want to connect to a RESTful endpoint that has a valid certificate, is there any configuration needed for spring restTemplate in that case?

Eugen Paraschiv
4 years ago
Reply to  Pawan H

Glad you’re enjoying the site Pawan.
If the certificate used by the API is valid – no, you don’t need anything special, that’s going to work out of the box.
Cheers,
Eugen.

Gleb Vlasyuk
Gleb Vlasyuk
3 years ago

OMG, you saved me a lot of time!:) Thank’s a lot

All Pereira
All Pereira
3 years ago

It’s a good sample that we can use. I appreciate for your time to make it for us. Thank you.

Comments are closed on this article!