Set properties from SslOptions for Jetty and JDK HTTP clients
Update `ClientHttpRequestFactoryBuilder` implementations for both Jetty and JDK to configure properties from SslOptions. Fixes gh-43077
This commit is contained in:
parent
065e7c190e
commit
ede1110e36
|
|
@ -22,9 +22,12 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.net.ssl.SSLParameters;
|
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -90,11 +93,20 @@ public class JdkClientHttpRequestFactoryBuilder
|
|||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(settings::connectTimeout).to(builder::connectTimeout);
|
||||
map.from(settings::sslBundle).as(SslBundle::createSslContext).to(builder::sslContext);
|
||||
map.from(settings::sslBundle).as(this::asSslParameters).to(builder::sslParameters);
|
||||
map.from(settings::redirects).as(this::asHttpClientRedirect).to(builder::followRedirects);
|
||||
this.httpClientCustomizer.accept(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private SSLParameters asSslParameters(SslBundle sslBundle) {
|
||||
SslOptions options = sslBundle.getOptions();
|
||||
SSLParameters parameters = new SSLParameters();
|
||||
parameters.setCipherSuites(options.getCiphers());
|
||||
parameters.setProtocols(options.getEnabledProtocols());
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private Redirect asHttpClientRedirect(Redirects redirects) {
|
||||
return switch (redirects) {
|
||||
case FOLLOW_WHEN_POSSIBLE, FOLLOW -> Redirect.NORMAL;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.http.client.JettyClientHttpRequestFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -156,10 +157,19 @@ public final class JettyClientHttpRequestFactoryBuilder
|
|||
}
|
||||
|
||||
private SslContextFactory.Client createSslContextFactory(SslBundle sslBundle) {
|
||||
SslOptions options = sslBundle.getOptions();
|
||||
SSLContext sslContext = sslBundle.createSslContext();
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
sslContextFactory.setSslContext(sslContext);
|
||||
return sslContextFactory;
|
||||
SslContextFactory.Client factory = new SslContextFactory.Client();
|
||||
factory.setSslContext(sslContext);
|
||||
if (options.getCiphers() != null) {
|
||||
factory.setIncludeCipherSuites(options.getCiphers());
|
||||
factory.setExcludeCipherSuites();
|
||||
}
|
||||
if (options.getEnabledProtocols() != null) {
|
||||
factory.setIncludeProtocols(options.getEnabledProtocols());
|
||||
factory.setExcludeProtocols();
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
private boolean followRedirects(Redirects redirects) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
|
@ -36,6 +37,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
|
||||
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
|
||||
|
|
@ -123,6 +125,27 @@ abstract class AbstractClientHttpRequestFactoryBuilderTests<T extends ClientHttp
|
|||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "GET", "POST" })
|
||||
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
|
||||
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory(0);
|
||||
webServerFactory.setSsl(ssl("TLS_AES_128_GCM_SHA256"));
|
||||
WebServer webServer = webServerFactory
|
||||
.getWebServer((context) -> context.addServlet("test", TestServlet.class).addMapping("/"));
|
||||
try {
|
||||
webServer.start();
|
||||
int port = webServer.getPort();
|
||||
URI uri = new URI("https://localhost:%s".formatted(port));
|
||||
ClientHttpRequestFactory requestFactory = this.builder.build(ClientHttpRequestFactorySettings
|
||||
.ofSslBundle(sslBundle(SslOptions.of(Set.of("TLS_AES_256_GCM_SHA384"), null))));
|
||||
ClientHttpRequest secureRequest = request(requestFactory, uri, httpMethod);
|
||||
assertThatExceptionOfType(SSLHandshakeException.class).isThrownBy(() -> secureRequest.execute().getBody());
|
||||
}
|
||||
finally {
|
||||
webServer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "GET", "POST", "PUT", "PATCH", "DELETE" })
|
||||
void redirectDefault(String httpMethod) throws Exception {
|
||||
|
|
@ -172,19 +195,26 @@ abstract class AbstractClientHttpRequestFactoryBuilderTests<T extends ClientHttp
|
|||
return factory.createRequest(uri, HttpMethod.valueOf(method));
|
||||
}
|
||||
|
||||
private Ssl ssl() {
|
||||
private Ssl ssl(String... ciphers) {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(ClientAuth.NEED);
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
if (ciphers.length > 0) {
|
||||
ssl.setCiphers(ciphers);
|
||||
}
|
||||
return ssl;
|
||||
}
|
||||
|
||||
protected final SslBundle sslBundle() {
|
||||
return sslBundle(SslOptions.NONE);
|
||||
}
|
||||
|
||||
protected final SslBundle sslBundle(SslOptions sslOptions) {
|
||||
JksSslStoreDetails storeDetails = JksSslStoreDetails.forLocation("classpath:test.jks");
|
||||
JksSslStoreBundle stores = new JksSslStoreBundle(storeDetails, storeDetails);
|
||||
return SslBundle.of(stores, SslBundleKey.of("password"));
|
||||
return SslBundle.of(stores, SslBundleKey.of("password"), sslOptions);
|
||||
}
|
||||
|
||||
protected HttpStatus getExpectedRedirect(HttpMethod httpMethod) {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ class ReflectiveComponentsClientHttpRequestFactoryBuilderTests
|
|||
.withMessage("Unable to set redirect follow using reflection");
|
||||
}
|
||||
|
||||
@Override
|
||||
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
|
||||
assertThatIllegalStateException().isThrownBy(() -> super.connectWithSslBundleAndOptionsMismatch(httpMethod))
|
||||
.withMessage("Unable to set SSL bundler using reflection");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWithClassCreatesFactory() {
|
||||
assertThat(ofTestRequestFactory().build()).isInstanceOf(TestClientHttpRequestFactory.class);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link SimpleClientHttpRequestFactoryBuilder}.
|
||||
*
|
||||
|
|
@ -47,6 +49,12 @@ class SimpleClientHttpRequestFactoryBuilderTests
|
|||
return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout");
|
||||
}
|
||||
|
||||
@Override
|
||||
void connectWithSslBundleAndOptionsMismatch(String httpMethod) throws Exception {
|
||||
assertThatIllegalStateException().isThrownBy(() -> super.connectWithSslBundleAndOptionsMismatch(httpMethod))
|
||||
.withMessage("SSL Options cannot be specified with Java connections");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "GET", "POST", "PUT", "DELETE" })
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Reference in New Issue