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:
committed by
Phillip Webb
parent
8e1f24f98f
commit
66db13b962
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user