From 77c468c95674e99fe423ca2a8195945140c27957 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 21 Apr 2023 13:32:34 -0700 Subject: [PATCH] Add test to check SSL RestTemplate requests work against server Add a test to `AbstractClientHttpRequestFactoriesTests` to ensure that SSL configuration works when calling a real Tomcat server. See gh-34810 --- .../client/ClientHttpRequestFactories.java | 29 ++++++++++- ...stractClientHttpRequestFactoriesTests.java | 51 +++++++++++++++++++ ...lientHttpRequestFactoriesOkHttp3Tests.java | 6 +++ ...lientHttpRequestFactoriesOkHttp4Tests.java | 6 +++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java index 416791cc2c..35ce1be0a2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/ClientHttpRequestFactories.java @@ -16,13 +16,16 @@ package org.springframework.boot.web.client; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.HttpURLConnection; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -220,7 +223,9 @@ public final class ClientHttpRequestFactories { static class Simple { static SimpleClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + SslBundle sslBundle = settings.sslBundle(); + SimpleClientHttpRequestFactory requestFactory = (sslBundle != null) + ? new SimpleClientHttpsRequestFactory(sslBundle) : new SimpleClientHttpRequestFactory(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout); map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout); @@ -228,6 +233,28 @@ public final class ClientHttpRequestFactories { return requestFactory; } + /** + * {@link SimpleClientHttpsRequestFactory} to configure SSL from an + * {@link SslBundle}. + */ + private static class SimpleClientHttpsRequestFactory extends SimpleClientHttpRequestFactory { + + private SslBundle sslBundle; + + SimpleClientHttpsRequestFactory(SslBundle sslBundle) { + this.sslBundle = sslBundle; + } + + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + if (this.sslBundle != null && connection instanceof HttpsURLConnection secureConnection) { + SSLSocketFactory socketFactory = this.sslBundle.createSslContext().getSocketFactory(); + secureConnection.setSSLSocketFactory(socketFactory); + } + } + + } + } /** diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/AbstractClientHttpRequestFactoriesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/AbstractClientHttpRequestFactoriesTests.java index 947f833254..44b6cb6ecc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/AbstractClientHttpRequestFactoriesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/AbstractClientHttpRequestFactoriesTests.java @@ -16,13 +16,30 @@ package org.springframework.boot.web.client; +import java.net.URI; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import javax.net.ssl.SSLHandshakeException; + import org.junit.jupiter.api.Test; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundleKey; +import org.springframework.boot.ssl.jks.JksSslStoreBundle; +import org.springframework.boot.ssl.jks.JksSslStoreDetails; +import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.Ssl.ClientAuth; +import org.springframework.boot.web.server.WebServer; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.StreamUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Base classes for testing of {@link ClientHttpRequestFactories} with different HTTP @@ -31,6 +48,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @param the {@link ClientHttpRequestFactory} to be produced * @author Andy Wilkinson */ +@DirtiesUrlFactories abstract class AbstractClientHttpRequestFactoriesTests { private final Class requestFactoryType; @@ -76,6 +94,39 @@ abstract class AbstractClientHttpRequestFactoriesTests insecureRequest.execute().getBody()); + JksSslStoreDetails storeDetails = JksSslStoreDetails.forLocation("classpath:test.jks"); + JksSslStoreBundle stores = new JksSslStoreBundle(storeDetails, storeDetails); + SslBundle sslBundle = SslBundle.of(stores, SslBundleKey.of("password")); + ClientHttpRequestFactory secureRequestFactory = ClientHttpRequestFactories + .get(ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(sslBundle)); + ClientHttpRequest secureRequest = secureRequestFactory.createRequest(uri, HttpMethod.GET); + String secureResponse = StreamUtils.copyToString(secureRequest.execute().getBody(), StandardCharsets.UTF_8); + assertThat(secureResponse).contains("HTTP Status 404 – Not Found"); + } + finally { + webServer.stop(); + } + } + protected abstract long connectTimeout(T requestFactory); protected abstract long readTimeout(T requestFactory); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java index 59e4c31444..35c0e509e0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp3Tests.java @@ -19,6 +19,7 @@ package org.springframework.boot.web.client; import java.io.File; import okhttp3.OkHttpClient; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; @@ -66,4 +67,9 @@ class ClientHttpRequestFactoriesOkHttp3Tests return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).readTimeoutMillis(); } + @Override + @Disabled("OkHostnameVerifier fails because the JSK doesn't have a type 2 SubjectAltName") + void connectWithSslBundle() throws Exception { + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java index 13158708f5..04b52ca5b6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/ClientHttpRequestFactoriesOkHttp4Tests.java @@ -19,6 +19,7 @@ package org.springframework.boot.web.client; import java.io.File; import okhttp3.OkHttpClient; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; @@ -64,4 +65,9 @@ class ClientHttpRequestFactoriesOkHttp4Tests return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).readTimeoutMillis(); } + @Override + @Disabled("OkHostnameVerifier fails because the JSK doesn't have a type 2 SubjectAltName") + void connectWithSslBundle() throws Exception { + } + }