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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <T> the {@link ClientHttpRequestFactory} to be produced
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@DirtiesUrlFactories
|
||||
abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpRequestFactory> {
|
||||
|
||||
private final Class<T> requestFactoryType;
|
||||
@@ -76,6 +94,39 @@ abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpReque
|
||||
assertThat(readTimeout((T) requestFactory)).isEqualTo(Duration.ofSeconds(120).toMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectWithSslBundle() throws Exception {
|
||||
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(ClientAuth.NEED);
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
webServerFactory.setSsl(ssl);
|
||||
WebServer webServer = webServerFactory.getWebServer();
|
||||
try {
|
||||
webServer.start();
|
||||
int port = webServer.getPort();
|
||||
URI uri = new URI("https://localhost:%s".formatted(port));
|
||||
ClientHttpRequestFactory insecureRequestFactory = ClientHttpRequestFactories
|
||||
.get(ClientHttpRequestFactorySettings.DEFAULTS);
|
||||
ClientHttpRequest insecureRequest = insecureRequestFactory.createRequest(uri, HttpMethod.GET);
|
||||
assertThatExceptionOfType(SSLHandshakeException.class)
|
||||
.isThrownBy(() -> 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);
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user