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
pull/35107/head
Phillip Webb 2 years ago
parent 72c1f667f5
commit 77c468c956

@ -16,13 +16,16 @@
package org.springframework.boot.web.client; package org.springframework.boot.web.client;
import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
@ -220,7 +223,9 @@ public final class ClientHttpRequestFactories {
static class Simple { static class Simple {
static SimpleClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) { 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(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout); map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout); map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
@ -228,6 +233,28 @@ public final class ClientHttpRequestFactories {
return requestFactory; 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; package org.springframework.boot.web.client;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import javax.net.ssl.SSLHandshakeException;
import org.junit.jupiter.api.Test; 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.http.client.ClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat; 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 * 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 * @param <T> the {@link ClientHttpRequestFactory} to be produced
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@DirtiesUrlFactories
abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpRequestFactory> { abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpRequestFactory> {
private final Class<T> requestFactoryType; private final Class<T> requestFactoryType;
@ -76,6 +94,39 @@ abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpReque
assertThat(readTimeout((T) requestFactory)).isEqualTo(Duration.ofSeconds(120).toMillis()); 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 connectTimeout(T requestFactory);
protected abstract long readTimeout(T requestFactory); protected abstract long readTimeout(T requestFactory);

@ -19,6 +19,7 @@ package org.springframework.boot.web.client;
import java.io.File; import java.io.File;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -66,4 +67,9 @@ class ClientHttpRequestFactoriesOkHttp3Tests
return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).readTimeoutMillis(); 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 java.io.File;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -64,4 +65,9 @@ class ClientHttpRequestFactoriesOkHttp4Tests
return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).readTimeoutMillis(); 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 {
}
} }

Loading…
Cancel
Save