Refactor web server support to use SslBundles

Update Tomcat, Jetty, Undertow and Netty servers so that an SslBundle
is used to apply SSL configuration. Existing `Ssl` properties are
internally adapted to an `SslBundle` using the `WebServerSslBundle`
class. Additionally, if `Ssl.getBundle()` returns a non-null value the
the `SslBundles` bean will be used to find a registered bundle by name.

See gh-34814
This commit is contained in:
Scott Frederick
2023-04-20 20:12:56 -07:00
committed by Phillip Webb
parent 8e1f24f98f
commit 66db13b962
48 changed files with 1028 additions and 1762 deletions

View File

@@ -37,6 +37,7 @@ import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
import org.springframework.boot.rsocket.netty.NettyRSocketServerFactory;
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
import org.springframework.boot.rsocket.server.RSocketServerFactory;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@@ -54,6 +55,7 @@ import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHa
* server port is configured, a new standalone RSocket server is created.
*
* @author Brian Clozel
* @author Scott Frederick
* @since 2.2.0
*/
@AutoConfiguration(after = RSocketStrategiesAutoConfiguration.class)
@@ -85,7 +87,7 @@ public class RSocketServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
RSocketServerFactory rSocketServerFactory(RSocketProperties properties, ReactorResourceFactory resourceFactory,
ObjectProvider<RSocketServerCustomizer> customizers) {
ObjectProvider<RSocketServerCustomizer> customizers, ObjectProvider<SslBundles> sslBundles) {
NettyRSocketServerFactory factory = new NettyRSocketServerFactory();
factory.setResourceFactory(resourceFactory);
factory.setTransport(properties.getServer().getTransport());
@@ -94,6 +96,7 @@ public class RSocketServerAutoConfiguration {
map.from(properties.getServer().getPort()).to(factory::setPort);
map.from(properties.getServer().getFragmentSize()).to(factory::setFragmentSize);
map.from(properties.getServer().getSsl()).to(factory::setSsl);
factory.setSslBundles(sslBundles.getIfAvailable());
factory.setRSocketServerCustomizers(customizers.orderedStream().toList());
return factory;
}

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.web.reactive;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -31,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@@ -45,6 +47,7 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
* {@link EnableAutoConfiguration Auto-configuration} for a reactive web server.
*
* @author Brian Clozel
* @author Scott Frederick
* @since 2.0.0
*/
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@@ -60,8 +63,9 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
public class ReactiveWebServerFactoryAutoConfiguration {
@Bean
public ReactiveWebServerFactoryCustomizer reactiveWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ReactiveWebServerFactoryCustomizer(serverProperties);
public ReactiveWebServerFactoryCustomizer reactiveWebServerFactoryCustomizer(ServerProperties serverProperties,
ObjectProvider<SslBundles> sslBundles) {
return new ReactiveWebServerFactoryCustomizer(serverProperties, sslBundles.getIfAvailable());
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.web.reactive;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
@@ -28,6 +29,7 @@ import org.springframework.core.Ordered;
*
* @author Brian Clozel
* @author Yunkun Huang
* @author Scott Frederick
* @since 2.0.0
*/
public class ReactiveWebServerFactoryCustomizer
@@ -35,8 +37,25 @@ public class ReactiveWebServerFactoryCustomizer
private final ServerProperties serverProperties;
private final SslBundles sslBundles;
/**
* Create a new {@link ReactiveWebServerFactoryCustomizer} instance.
* @param serverProperties the server properties
*/
public ReactiveWebServerFactoryCustomizer(ServerProperties serverProperties) {
this(serverProperties, null);
}
/**
* Create a new {@link ReactiveWebServerFactoryCustomizer} instance.
* @param serverProperties the server properties
* @param sslBundles the SSL bundles
* @since 3.1.0
*/
public ReactiveWebServerFactoryCustomizer(ServerProperties serverProperties, SslBundles sslBundles) {
this.serverProperties = serverProperties;
this.sslBundles = sslBundles;
}
@Override
@@ -53,6 +72,7 @@ public class ReactiveWebServerFactoryCustomizer
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
map.from(() -> this.sslBundles).to(factory::setSslBundles);
}
}

View File

@@ -33,8 +33,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -57,9 +59,10 @@ import org.springframework.web.filter.ForwardedHeaderFilter;
* @author Ivan Sopov
* @author Brian Clozel
* @author Stephane Nicoll
* @author Scott Frederick
* @since 2.0.0
*/
@AutoConfiguration
@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@@ -73,9 +76,9 @@ public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers, ObjectProvider<SslBundles> sslBundles) {
return new ServletWebServerFactoryCustomizer(serverProperties, webListenerRegistrars.orderedStream().toList(),
cookieSameSiteSuppliers.orderedStream().toList());
cookieSameSiteSuppliers.orderedStream().toList(), sslBundles.getIfAvailable());
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.WebListenerRegistrar;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
@@ -36,6 +37,7 @@ import org.springframework.util.CollectionUtils;
* @author Stephane Nicoll
* @author Olivier Lamy
* @author Yunkun Huang
* @author Scott Frederick
* @since 2.0.0
*/
public class ServletWebServerFactoryCustomizer
@@ -47,20 +49,24 @@ public class ServletWebServerFactoryCustomizer
private final List<CookieSameSiteSupplier> cookieSameSiteSuppliers;
private final SslBundles sslBundles;
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this(serverProperties, Collections.emptyList());
}
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties,
List<WebListenerRegistrar> webListenerRegistrars) {
this(serverProperties, webListenerRegistrars, null);
this(serverProperties, webListenerRegistrars, null, null);
}
ServletWebServerFactoryCustomizer(ServerProperties serverProperties,
List<WebListenerRegistrar> webListenerRegistrars, List<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
List<WebListenerRegistrar> webListenerRegistrars, List<CookieSameSiteSupplier> cookieSameSiteSuppliers,
SslBundles sslBundles) {
this.serverProperties = serverProperties;
this.webListenerRegistrars = webListenerRegistrars;
this.cookieSameSiteSuppliers = cookieSameSiteSuppliers;
this.sslBundles = sslBundles;
}
@Override
@@ -84,12 +90,11 @@ public class ServletWebServerFactoryCustomizer
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
registrar.register(factory);
}
if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
}
map.from(() -> this.sslBundles).to(factory::setSslBundles);
map.from(() -> this.cookieSameSiteSuppliers)
.whenNot(CollectionUtils::isEmpty)
.to(factory::setCookieSameSiteSuppliers);
this.webListenerRegistrars.forEach((registrar) -> registrar.register(factory));
}
}

View File

@@ -2639,6 +2639,10 @@
"level": "error"
}
},
{
"name": "spring.rsocket.server.ssl.bundle",
"description": "The name of a configured SSL bundle."
},
{
"name": "spring.rsocket.server.ssl.certificate",
"description": "Path to a PEM-encoded SSL certificate file."

View File

@@ -16,13 +16,16 @@
package org.springframework.boot.autoconfigure.rsocket;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer;
import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
import org.springframework.boot.rsocket.server.RSocketServerFactory;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
@@ -44,6 +47,7 @@ import static org.mockito.Mockito.mock;
*
* @author Brian Clozel
* @author Verónica Vásquez
* @author Scott Frederick
*/
class RSocketServerAutoConfigurationTests {
@@ -134,6 +138,32 @@ class RSocketServerAutoConfigurationTests {
.hasFieldOrPropertyWithValue("ssl.keyPassword", "password"));
}
@Test
@Disabled
void shouldUseSslWhenRocketServerSslIsConfiguredWithSslBundle() {
reactiveWebContextRunner()
.withPropertyValues("spring.rsocket.server.port=0", "spring.rsocket.server.ssl.bundle=test-bundle",
"spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:rsocket/test.jks",
"spring.ssl.bundle.jks.test-bundle.key.password=password")
.run((context) -> assertThat(context).hasSingleBean(RSocketServerFactory.class)
.hasSingleBean(RSocketServerBootstrap.class)
.hasSingleBean(RSocketServerCustomizer.class)
.getBean(RSocketServerFactory.class)
.hasFieldOrPropertyWithValue("sslBundle.details.keyStore", "classpath:rsocket/test.jks")
.hasFieldOrPropertyWithValue("sslBundle.details.keyPassword", "password"));
}
@Test
void shouldFailWhenSslIsConfiguredWithMissingBundle() {
reactiveWebContextRunner()
.withPropertyValues("spring.rsocket.server.port=0", "spring.rsocket.server.ssl.bundle=test-bundle")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(NoSuchSslBundleException.class)
.withFailMessage("SSL bundle name 'test-bundle' is not valid");
});
}
@Test
void shouldUseCustomServerBootstrap() {
contextRunner().withUserConfiguration(CustomServerBootstrapConfig.class)
@@ -164,7 +194,7 @@ class RSocketServerAutoConfigurationTests {
private ReactiveWebApplicationContextRunner reactiveWebContextRunner() {
return new ReactiveWebApplicationContextRunner().withUserConfiguration(BaseConfiguration.class)
.withConfiguration(AutoConfigurations.of(RSocketServerAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(RSocketServerAutoConfiguration.class, SslAutoConfiguration.class));
}
@Configuration(proxyBeanMethods = false)

View File

@@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test;
import reactor.netty.http.server.HttpServer;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
@@ -64,13 +66,15 @@ import static org.mockito.Mockito.mock;
* @author Brian Clozel
* @author Raheela Aslam
* @author Madhura Bhave
* @author Scott Frederick
*/
@DirtiesUrlFactories
class ReactiveWebServerFactoryAutoConfigurationTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(
AnnotationConfigReactiveWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class));
.withConfiguration(
AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class, SslAutoConfiguration.class));
@Test
void createFromConfigClass() {
@@ -118,6 +122,17 @@ class ReactiveWebServerFactoryAutoConfigurationTests {
.isInstanceOf(TomcatReactiveWebServerFactory.class));
}
@Test
void webServerFailsWithInvalidSslBundle() {
this.contextRunner.withUserConfiguration(HttpHandlerConfiguration.class)
.withPropertyValues("server.port=0", "server.ssl.bundle=test-bundle")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure().getCause()).isInstanceOf(NoSuchSslBundleException.class)
.withFailMessage("test");
});
}
@Test
void tomcatConnectorCustomizerBeanIsAddedToFactory() {
ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.Ssl;
@@ -36,16 +38,19 @@ import static org.mockito.Mockito.mock;
*
* @author Brian Clozel
* @author Yunkun Huang
* @author Scott Frederick
*/
class ReactiveWebServerFactoryCustomizerTests {
private final ServerProperties properties = new ServerProperties();
private final SslBundles sslBundles = new DefaultSslBundleRegistry();
private ReactiveWebServerFactoryCustomizer customizer;
@BeforeEach
void setup() {
this.customizer = new ReactiveWebServerFactoryCustomizer(this.properties);
this.customizer = new ReactiveWebServerFactoryCustomizer(this.properties, this.sslBundles);
}
@Test
@@ -72,6 +77,7 @@ class ReactiveWebServerFactoryCustomizerTests {
this.properties.setSsl(ssl);
this.customizer.customize(factory);
then(factory).should().setSsl(ssl);
then(factory).should().setSslBundles(this.sslBundles);
}
@Test