Hi Guys,

Spring Boot WebFlux comes with WebClient which we use to make HTTP or HTTPS calls. There are situations where might have disable SSL verification while making HTTPS calls, especially when dealing with self-signed certificates or while working in testing environments, or sometimes when we want to get rid of all type of exceptions related to SSL like handshake or pkix exception (please don’t do it if you are doing it). So, let’s see how we can disable SSL check in WebClient.

Let’s jump into the code.

Please make sure you have the following dependency in the pom.xml file.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Next, create a @Configuration class for WebClient.

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;

import javax.net.ssl.SSLException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

import io.netty.channel.ChannelOption;
import io.netty.channel.unix.UnixChannelOption;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.resolver.DefaultAddressResolverGroup;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;

@Configuration
public class WebClientConfiguration {

  private static final String CONNECTION_PROVIDER_NAME = "customConnectionProvider";
  private static final int MAX_CONNECTIONS = 10;
  private static final int ACQUIRE_TIMEOUT = 60;
  private static final int CONNECT_TIMEOUT_MILLIS = 6000;
  private static final int READ_WRITE_TIMEOUT_SECONDS = 60;
  private static final int TIMEOUT_SECONDS = 60;
  private static final int MAX_PENDING_ACQUIRES = 5;
  
  private static final Iterable<String> allowedCiphers = List.of("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384");
  
  @Primary
  @Bean(name = "webclientwithnossl")
  WebClient.Builder webClientBuilderStage() throws SSLException {
      ConnectionProvider connectionProvider = buildConnectionProvider();
      SslContext sslContext = buildSslContext();
      HttpClient httpClient = buildHttpClient(connectionProvider, sslContext);

      return buildWebClient(httpClient);
  }

  private ConnectionProvider buildConnectionProvider() {
      return ConnectionProvider.builder(CONNECTION_PROVIDER_NAME)
      	.maxConnections(MAX_CONNECTIONS)
        .maxLifeTime(Duration.ofSeconds(TIMEOUT_SECONDS))
        .pendingAcquireTimeout(Duration.ofMillis(ACQUIRE_TIMEOUT))
        .pendingAcquireMaxCount(MAX_PENDING_ACQUIRES)
        .maxIdleTime(Duration.ofSeconds(ACQUIRE_TIMEOUT))
        .lifo()
        .build();
  }

  private SslContext buildSslContext() throws SSLException {
    return SslContextBuilder.forClient()
        .protocols("SSLv3","TLSv1","TLSv1.1","TLSv1.2")
        .ciphers(allowedCiphers)
        .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
  }

  private HttpClient buildHttpClient(ConnectionProvider connectionProvider, SslContext sslContext) {
      return HttpClient.create(connectionProvider)
      		.compress(true) 
      		.followRedirect(true)
      		.resolver(DefaultAddressResolverGroup.INSTANCE)
      		.secure(t -> t.sslContext(sslContext).handshakeTimeout(Duration.ofSeconds(TIMEOUT_SECONDS)))
      		.keepAlive(true)
      		.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MILLIS)
                .doOnConnected(connection -> {
                    connection.addHandlerLast(new ReadTimeoutHandler(READ_WRITE_TIMEOUT_SECONDS));
                    connection.addHandlerLast(new WriteTimeoutHandler(READ_WRITE_TIMEOUT_SECONDS));
                })
                .option(UnixChannelOption.SO_KEEPALIVE, true);
  }

  private WebClient.Builder buildWebClient(HttpClient httpClient) {
      return WebClient.builder()
      	.defaultHeader(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.toString())
        .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
        .clientConnector(new ReactorClientHttpConnector(httpClient));
  }
}

Please note 3 important things in the above class

1. The list of allowed ciphers.

private static final Iterable<String> allowedCiphers = List.of("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384");

These are the cipher suites that are allowed for TLS (Transport Layer Security) connections. Each string in the list represents a specific cipher suite, which is a combination of cryptographic algorithms used to secure network communications. For example, “TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384” represents a cipher suite that uses the ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) key exchange, RSA authentication, AES-256 for encryption, and GCM (Galois/Counter Mode) for message authentication. There are a lot of material on internet on these cipher suites, you can have a look at it if you need more details.

2. The SSLContext object creation

private SslContext buildSslContext() throws SSLException {
    return SslContextBuilder.forClient()
       .protocols("SSLv3","TLSv1","TLSv1.1","TLSv1.2")
       .ciphers(allowedCiphers)
       .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
}

To disable SSL verification, InsecureTrustManagerFactory has been used in the SSL context builder. This will basically do the trick we need.

So, do you need the allowed ciphers in the first place or the protocols (“SSLv3″,”TLSv1″,”TLSv1.1″,”TLSv1.2”) mentioned in SSL builder? The answer is yes because in certain environment setup, you will still get SSLException if you don’t use it. However, you can still use the below code with allowed ciphers and protocols and it might work for you.

private SslContext buildSslContext() throws SSLException {
    return SslContextBuilder.forClient()
            .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
}

3. In order to use this SslContext object, you have to use HttpClient from netty.

private HttpClient buildHttpClient(ConnectionProvider connectionProvider, SslContext sslContext) {
      return HttpClient.create(connectionProvider)
      		// .....
      		// .....
      		.secure(t -> t.sslContext(sslContext).handshakeTimeout(Duration.ofSeconds(TIMEOUT_SECONDS)))
                // .....

 

Finally, use the custom WebClient in your service or controller:

@Autowired
@Qualifier("webclientwithnossl")
private WebClient.Builder webClientBuilder;

// Use webClientBuilder to create WebClient instances

That’s it.

 

Hope you like it.

Have a nice day ahead.

 

Loading