dardo
dardo

Reputation: 4970

WebClient vs. RestTemplate SSL Connection

I'm calling the same API endpoint once with WebClient, and once with RestTemplate. The RestTemplate call succeeds, the WebClient call fails due to handshake_failure.

Here's the configuration for my WebClient bean.

@Bean
WebClient webClient(String baseUrl,
                    @Value("classpath:bundle.pem") Resource pemBundleResource) throws Exception {
    final SslContext sslContext = SslContextBuilder
        .forClient()
        .trustManager(pemBundleResource.getInputStream())
        .build();

    HttpClient httpClient = HttpClient
        .create()
        .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));

    return WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .baseUrl(baseUrl)
        .build();
}

I've also tried an InsecureTrustManagerFactory.INSTANCE to ignore SSL altogether, but that request fails as well with the same error which tells me the configuration might not be getting used.

Any help would be appreciated!

Here's the stack trace:


reactor.core.Exceptions$ReactiveException: javax.net.ssl.SSLException: Received fatal alert: handshake_failure

    at reactor.core.Exceptions.propagate(Exceptions.java:326)
    at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:91)
    at reactor.core.publisher.Mono.block(Mono.java:1494)
    at com.example.APITestsIT.callApi(APITestsIT.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:93)
        ... 32 more
Caused by: javax.net.ssl.SSLException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
    at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1647)
    at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1615)
    at sun.security.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1781)
    at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1070)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:896)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:766)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
    at io.netty.handler.ssl.SslHandler$SslEngineType$3.unwrap(SslHandler.java:295)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1330)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1225)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1272)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:502)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:441)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:278)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:337)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:345)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:677)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:612)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:529)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:491)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905)
    at java.lang.Thread.run(Thread.java:748)

Upvotes: 4

Views: 4651

Answers (2)

Valchkou
Valchkou

Reputation: 379

works with spring-boot 2.5.2 and httpclient5

import lombok.extern.slf4j.*;
import org.apache.hc.client5.http.config.*;
import org.apache.hc.client5.http.impl.async.*;
import org.apache.hc.client5.http.impl.nio.*;
import org.apache.hc.client5.http.nio.*;
import org.apache.hc.client5.http.ssl.*;
import org.apache.hc.core5.http.config.*;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.nio.ssl.*;
import org.apache.hc.core5.ssl.*;
import org.apache.hc.core5.util.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.context.annotation.*;
import org.springframework.http.client.reactive.*;
import org.springframework.web.reactive.function.client.*;

import javax.net.ssl.*;
import java.security.*;
import java.util.concurrent.*;

@Slf4j
@Configuration
public class WebClientConfig {

@Value("${webclient.connection-timeout-millis:60000}")
protected long connectionTimeout;

@Value("${webclient.response-timeout-millis:300000}")
protected long responseTimeout;

@Value("${webclient.request-timeout-millis:300000}")
protected long requestTimeout;

@Bean
public WebClient webClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {

    RequestConfig config = RequestConfig.custom()
            .setConnectTimeout(connectionTimeout, TimeUnit.MILLISECONDS)
            .setConnectionRequestTimeout(requestTimeout, TimeUnit.MILLISECONDS)
            .setResponseTimeout(responseTimeout, TimeUnit.MILLISECONDS)
            .setHardCancellationEnabled(true)
            .setConnectionKeepAlive(TimeValue.ofHours(1))
            .build();

    final SSLContext sslcontext = SSLContexts.custom()
            .loadTrustMaterial(null, new TrustAllStrategy())
            .build();

    final Lookup<TlsStrategy> tlsStrategyLookup = RegistryBuilder.<TlsStrategy>create()
            .register("https", new DefaultClientTlsStrategy(sslcontext))
            .build();

    final AsyncClientConnectionManager cm = new PoolingAsyncClientConnectionManager(tlsStrategyLookup);

    CloseableHttpAsyncClient client = HttpAsyncClients
            .custom()
            .useSystemProperties()
            .setConnectionManager(cm)
            .setDefaultRequestConfig(config)
            .build();

    // init spring-boot wrapper for httpclient5
    WebClient wc = WebClient
            .builder()
            .clientConnector(new HttpComponentsClientHttpConnector(client))
            .build();


    return wc;
}
}

Upvotes: 0

jiaoshang
jiaoshang

Reputation: 91

This one might work in your case,

# The ciphers which are needed
val allowedCiphers = listOf("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384")

@Bean
fun webClient(): WebClient {
    val sslContext = SslContextBuilder
            .forClient()
            .protocols("SSLv3","TLSv1","TLSv1.1","TLSv1.2")
            .ciphers(allowedCiphers)
            .build();

    val httpClient = HttpClient.create()
            .secure { it.sslContext(sslContext) }

    return WebClient.builder()
            .clientConnector(ReactorClientHttpConnector(httpClient))
            .baseUrl(baseUrl)
            .build()
}

Upvotes: 4

Related Questions