mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Merge branch '4.0.x' into 4.1.x
Closes gh-51457
This commit is contained in:
+1
@@ -46,6 +46,7 @@ configurations {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.springframework:spring-webflux")
|
compileOnly("org.springframework:spring-webflux")
|
||||||
|
compileOnly("org.springframework.boot:spring-boot-starter-tomcat")
|
||||||
|
|
||||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||||
implementation("org.springframework.boot:spring-boot-starter")
|
implementation("org.springframework.boot:spring-boot-starter")
|
||||||
|
|||||||
-10
@@ -16,16 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.boot.sni.server;
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.net.JarURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
|||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-present 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.catalina.connector.Connector;
|
||||||
|
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||||
|
import org.apache.tomcat.util.net.SSLHostConfig;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.ssl.SslBundleRegistry;
|
||||||
|
import org.springframework.boot.ssl.SslBundles;
|
||||||
|
import org.springframework.boot.tomcat.TomcatConnectorCustomizer;
|
||||||
|
import org.springframework.boot.tomcat.TomcatWebServer;
|
||||||
|
import org.springframework.boot.tomcat.TomcatWebServerFactory;
|
||||||
|
import org.springframework.boot.web.server.WebServer;
|
||||||
|
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||||
|
import org.springframework.boot.web.server.context.WebServerInitializedEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a customization to each of Tomcat's {@code SSLHostConfig} instances and reports
|
||||||
|
* their state once the server is running and again after an SSL bundle has been reloaded.
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@ConditionalOnClass(TomcatWebServerFactory.class)
|
||||||
|
class TomcatSslHostConfigConfiguration {
|
||||||
|
|
||||||
|
private static final int CUSTOMIZED_SESSION_TIMEOUT = 12345;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
WebServerFactoryCustomizer<TomcatWebServerFactory> sslHostConfigCustomizer() {
|
||||||
|
return (factory) -> factory.addConnectorCustomizers(new SslHostConfigCustomizer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ApplicationListener<WebServerInitializedEvent> sslHostConfigReporter(SslBundles sslBundles) {
|
||||||
|
return new SslHostConfigReporter(sslBundles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void forEachSslHostConfig(Connector connector, Consumer<SSLHostConfig> action) {
|
||||||
|
if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol<?> protocol) {
|
||||||
|
for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
|
||||||
|
action.accept(sslHostConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class SslHostConfigCustomizer implements TomcatConnectorCustomizer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(Connector connector) {
|
||||||
|
forEachSslHostConfig(connector,
|
||||||
|
(sslHostConfig) -> sslHostConfig.setSessionTimeout(CUSTOMIZED_SESSION_TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class SslHostConfigReporter implements ApplicationListener<WebServerInitializedEvent> {
|
||||||
|
|
||||||
|
private final SslBundles sslBundles;
|
||||||
|
|
||||||
|
SslHostConfigReporter(SslBundles sslBundles) {
|
||||||
|
this.sslBundles = sslBundles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(WebServerInitializedEvent event) {
|
||||||
|
WebServer webServer = event.getWebServer();
|
||||||
|
if (!(webServer instanceof TomcatWebServer tomcatWebServer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Connector connector = tomcatWebServer.getTomcat().getConnector();
|
||||||
|
report(connector, "start");
|
||||||
|
reloadBundles();
|
||||||
|
report(connector, "reload");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadBundles() {
|
||||||
|
if (!(this.sslBundles instanceof SslBundleRegistry registry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String name : new String[] { "default", "alt" }) {
|
||||||
|
registry.updateBundle(name, this.sslBundles.getBundle(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void report(Connector connector, String phase) {
|
||||||
|
forEachSslHostConfig(connector,
|
||||||
|
(sslHostConfig) -> System.out.println(">>>>> on " + phase + ", host="
|
||||||
|
+ sslHostConfig.getHostName() + ", port=" + connector.getPort()
|
||||||
|
+ ", sessionTimeout=" + sslHostConfig.getSessionTimeout()
|
||||||
|
+ ", certificates.size=" + sslHostConfig.getCertificates().size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
@@ -43,6 +43,7 @@ configurations {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("jakarta.servlet:jakarta.servlet-api:6.0.0")
|
compileOnly("jakarta.servlet:jakarta.servlet-api:6.0.0")
|
||||||
|
compileOnly("org.springframework.boot:spring-boot-starter-tomcat")
|
||||||
|
|
||||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||||
implementation("org.springframework.boot:spring-boot-starter-webmvc") {
|
implementation("org.springframework.boot:spring-boot-starter-webmvc") {
|
||||||
|
|||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-present 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.sni.server;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.catalina.connector.Connector;
|
||||||
|
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||||
|
import org.apache.tomcat.util.net.SSLHostConfig;
|
||||||
|
|
||||||
|
import org.springframework.boot.ssl.SslBundleRegistry;
|
||||||
|
import org.springframework.boot.ssl.SslBundles;
|
||||||
|
import org.springframework.boot.tomcat.TomcatConnectorCustomizer;
|
||||||
|
import org.springframework.boot.tomcat.TomcatWebServer;
|
||||||
|
import org.springframework.boot.tomcat.TomcatWebServerFactory;
|
||||||
|
import org.springframework.boot.web.server.WebServer;
|
||||||
|
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||||
|
import org.springframework.boot.web.server.context.WebServerInitializedEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a customization to each of Tomcat's {@code SSLHostConfig} instances and reports
|
||||||
|
* their state once the server is running and again after an SSL bundle has been reloaded.
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
class TomcatSslHostConfigConfiguration {
|
||||||
|
|
||||||
|
private static final int CUSTOMIZED_SESSION_TIMEOUT = 12345;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
WebServerFactoryCustomizer<TomcatWebServerFactory> sslHostConfigCustomizer() {
|
||||||
|
return (factory) -> factory.addConnectorCustomizers(new SslHostConfigCustomizer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ApplicationListener<WebServerInitializedEvent> sslHostConfigReporter(SslBundles sslBundles) {
|
||||||
|
return new SslHostConfigReporter(sslBundles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void forEachSslHostConfig(Connector connector, Consumer<SSLHostConfig> action) {
|
||||||
|
if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol<?> protocol) {
|
||||||
|
for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
|
||||||
|
action.accept(sslHostConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class SslHostConfigCustomizer implements TomcatConnectorCustomizer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(Connector connector) {
|
||||||
|
forEachSslHostConfig(connector,
|
||||||
|
(sslHostConfig) -> sslHostConfig.setSessionTimeout(CUSTOMIZED_SESSION_TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class SslHostConfigReporter implements ApplicationListener<WebServerInitializedEvent> {
|
||||||
|
|
||||||
|
private final SslBundles sslBundles;
|
||||||
|
|
||||||
|
SslHostConfigReporter(SslBundles sslBundles) {
|
||||||
|
this.sslBundles = sslBundles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(WebServerInitializedEvent event) {
|
||||||
|
WebServer webServer = event.getWebServer();
|
||||||
|
if (!(webServer instanceof TomcatWebServer tomcatWebServer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Connector connector = tomcatWebServer.getTomcat().getConnector();
|
||||||
|
report(connector, "start");
|
||||||
|
reloadBundles();
|
||||||
|
report(connector, "reload");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadBundles() {
|
||||||
|
if (!(this.sslBundles instanceof SslBundleRegistry registry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String name : new String[] { "default", "alt" }) {
|
||||||
|
registry.updateBundle(name, this.sslBundles.getBundle(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void report(Connector connector, String phase) {
|
||||||
|
forEachSslHostConfig(connector,
|
||||||
|
(sslHostConfig) -> System.out.println(">>>>> on " + phase + ", host="
|
||||||
|
+ sslHostConfig.getHostName() + ", port=" + connector.getPort()
|
||||||
|
+ ", sessionTimeout=" + sslHostConfig.getSessionTimeout()
|
||||||
|
+ ", certificates.size=" + sslHostConfig.getCertificates().size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+25
@@ -48,6 +48,10 @@ class SniIntegrationTests {
|
|||||||
|
|
||||||
public static final String ALT_SERVER_NAME = "hello-alt.example.com";
|
public static final String ALT_SERVER_NAME = "hello-alt.example.com";
|
||||||
|
|
||||||
|
private static final String DEFAULT_SERVER_NAME = "_default_";
|
||||||
|
|
||||||
|
private static final int CUSTOMIZED_SESSION_TIMEOUT = 12345;
|
||||||
|
|
||||||
private static final Integer SERVER_PORT = 8443;
|
private static final Integer SERVER_PORT = 8443;
|
||||||
|
|
||||||
private static final Network SHARED_NETWORK = Network.newNetwork();
|
private static final Network SHARED_NETWORK = Network.newNetwork();
|
||||||
@@ -66,6 +70,9 @@ class SniIntegrationTests {
|
|||||||
}
|
}
|
||||||
String serverLogs = serverContainer.getLogs();
|
String serverLogs = serverContainer.getLogs();
|
||||||
assertThat(serverLogs).contains(SERVER_START_MESSAGES.get(server));
|
assertThat(serverLogs).contains(SERVER_START_MESSAGES.get(server));
|
||||||
|
if ("tomcat".equals(server)) {
|
||||||
|
assertSslHostConfigCustomizationsRetained(serverContainer);
|
||||||
|
}
|
||||||
try (ApplicationContainer clientContainer = new ClientApplicationContainer()) {
|
try (ApplicationContainer clientContainer = new ClientApplicationContainer()) {
|
||||||
clientContainer.start();
|
clientContainer.start();
|
||||||
Awaitility.await().atMost(Duration.ofSeconds(60)).until(() -> !clientContainer.isRunning());
|
Awaitility.await().atMost(Duration.ofSeconds(60)).until(() -> !clientContainer.isRunning());
|
||||||
@@ -78,6 +85,24 @@ class SniIntegrationTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertSslHostConfigCustomizationsRetained(ApplicationContainer serverContainer) {
|
||||||
|
for (String phase : new String[] { "start", "reload" }) {
|
||||||
|
for (String serverName : new String[] { PRIMARY_SERVER_NAME, ALT_SERVER_NAME, DEFAULT_SERVER_NAME }) {
|
||||||
|
String expected = ">>>>> on " + phase + ", host=" + serverName + ", port=" + SERVER_PORT
|
||||||
|
+ ", sessionTimeout=" + CUSTOMIZED_SESSION_TIMEOUT + ", certificates.size=1";
|
||||||
|
try {
|
||||||
|
Awaitility.await()
|
||||||
|
.atMost(Duration.ofSeconds(60))
|
||||||
|
.until(() -> serverContainer.getLogs().contains(expected));
|
||||||
|
}
|
||||||
|
catch (ConditionTimeoutException ex) {
|
||||||
|
assertThat(serverContainer.getLogs()).contains(expected);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void assertServerCalledWithName(String clientLogs, String serverName) {
|
private void assertServerCalledWithName(String clientLogs, String serverName) {
|
||||||
assertThat(clientLogs).contains("Calling server at 'https://" + serverName + ":8443/'")
|
assertThat(clientLogs).contains("Calling server at 'https://" + serverName + ":8443/'")
|
||||||
.contains("Hello from https://" + serverName + ":8443/");
|
.contains("Hello from https://" + serverName + ":8443/");
|
||||||
|
|||||||
+31
-7
@@ -17,8 +17,10 @@
|
|||||||
package org.springframework.boot.tomcat;
|
package org.springframework.boot.tomcat;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.catalina.connector.Connector;
|
import org.apache.catalina.connector.Connector;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
@@ -64,9 +66,15 @@ public class SslConnectorCustomizer {
|
|||||||
|
|
||||||
public void update(@Nullable String serverName, SslBundle updatedSslBundle) {
|
public void update(@Nullable String serverName, SslBundle updatedSslBundle) {
|
||||||
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) this.connector.getProtocolHandler();
|
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) this.connector.getProtocolHandler();
|
||||||
String host = (serverName != null) ? serverName : protocol.getDefaultSSLHostConfigName();
|
String hostName = (serverName != null) ? serverName : protocol.getDefaultSSLHostConfigName();
|
||||||
this.logger.debug("SSL Bundle for host " + host + " has been updated, reloading SSL configuration");
|
this.logger.debug("SSL Bundle for host " + hostName + " has been updated, reloading SSL configuration");
|
||||||
addSslHostConfig(protocol, host, updatedSslBundle);
|
SSLHostConfig sslHostConfig = findSslHostConfig(protocol, hostName);
|
||||||
|
if (sslHostConfig == null) {
|
||||||
|
addSslHostConfig(protocol, hostName, updatedSslBundle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applySslBundle(protocol, sslHostConfig, updatedSslBundle);
|
||||||
|
protocol.addSslHostConfig(sslHostConfig, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void customize(SslBundle sslBundle, Map<String, SslBundle> serverNameSslBundles) {
|
public void customize(SslBundle sslBundle, Map<String, SslBundle> serverNameSslBundles) {
|
||||||
@@ -93,20 +101,27 @@ public class SslConnectorCustomizer {
|
|||||||
serverNameSslBundles.forEach((serverName, bundle) -> addSslHostConfig(protocol, serverName, bundle));
|
serverNameSslBundles.forEach((serverName, bundle) -> addSslHostConfig(protocol, serverName, bundle));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSslHostConfig(AbstractHttp11Protocol<?> protocol, String serverName, SslBundle sslBundle) {
|
private void addSslHostConfig(AbstractHttp11Protocol<?> protocol, String hostName, SslBundle sslBundle) {
|
||||||
SSLHostConfig sslHostConfig = new SSLHostConfig();
|
SSLHostConfig sslHostConfig = new SSLHostConfig();
|
||||||
sslHostConfig.setHostName(serverName);
|
sslHostConfig.setHostName(hostName);
|
||||||
configureSslClientAuth(sslHostConfig);
|
configureSslClientAuth(sslHostConfig);
|
||||||
applySslBundle(protocol, sslHostConfig, sslBundle);
|
applySslBundle(protocol, sslHostConfig, sslBundle);
|
||||||
protocol.addSslHostConfig(sslHostConfig, true);
|
protocol.addSslHostConfig(sslHostConfig, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Nullable SSLHostConfig findSslHostConfig(AbstractHttp11Protocol<?> protocol, String hostName) {
|
||||||
|
return Arrays.stream(protocol.findSslHostConfigs())
|
||||||
|
.filter((candidate) -> hostName.equalsIgnoreCase(candidate.getHostName()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
private void applySslBundle(AbstractHttp11Protocol<?> protocol, SSLHostConfig sslHostConfig, SslBundle sslBundle) {
|
private void applySslBundle(AbstractHttp11Protocol<?> protocol, SSLHostConfig sslHostConfig, SslBundle sslBundle) {
|
||||||
SslBundleKey key = sslBundle.getKey();
|
SslBundleKey key = sslBundle.getKey();
|
||||||
SslStoreBundle stores = sslBundle.getStores();
|
SslStoreBundle stores = sslBundle.getStores();
|
||||||
SslOptions options = sslBundle.getOptions();
|
SslOptions options = sslBundle.getOptions();
|
||||||
sslHostConfig.setSslProtocol(sslBundle.getProtocol());
|
sslHostConfig.setSslProtocol(sslBundle.getProtocol());
|
||||||
SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED);
|
SSLHostConfigCertificate certificate = getCertificate(sslHostConfig);
|
||||||
String keystorePassword = (stores.getKeyStorePassword() != null) ? stores.getKeyStorePassword() : "";
|
String keystorePassword = (stores.getKeyStorePassword() != null) ? stores.getKeyStorePassword() : "";
|
||||||
certificate.setCertificateKeystorePassword(keystorePassword);
|
certificate.setCertificateKeystorePassword(keystorePassword);
|
||||||
if (key.getPassword() != null) {
|
if (key.getPassword() != null) {
|
||||||
@@ -115,12 +130,21 @@ public class SslConnectorCustomizer {
|
|||||||
if (key.getAlias() != null) {
|
if (key.getAlias() != null) {
|
||||||
certificate.setCertificateKeyAlias(key.getAlias());
|
certificate.setCertificateKeyAlias(key.getAlias());
|
||||||
}
|
}
|
||||||
sslHostConfig.addCertificate(certificate);
|
|
||||||
configureCiphers(options, sslHostConfig);
|
configureCiphers(options, sslHostConfig);
|
||||||
configureSslStores(sslHostConfig, certificate, stores);
|
configureSslStores(sslHostConfig, certificate, stores);
|
||||||
configureEnabledProtocols(sslHostConfig, options);
|
configureEnabledProtocols(sslHostConfig, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SSLHostConfigCertificate getCertificate(SSLHostConfig sslHostConfig) {
|
||||||
|
Set<SSLHostConfigCertificate> certificates = sslHostConfig.getCertificates();
|
||||||
|
if (certificates.size() == 1) {
|
||||||
|
return certificates.iterator().next();
|
||||||
|
}
|
||||||
|
SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED);
|
||||||
|
sslHostConfig.addCertificate(certificate);
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
private void configureCiphers(SslOptions options, SSLHostConfig sslHostConfig) {
|
private void configureCiphers(SslOptions options, SSLHostConfig sslHostConfig) {
|
||||||
CipherConfiguration cipherConfiguration = CipherConfiguration.from(options);
|
CipherConfiguration cipherConfiguration = CipherConfiguration.from(options);
|
||||||
if (cipherConfiguration != null) {
|
if (cipherConfiguration != null) {
|
||||||
|
|||||||
+42
@@ -22,6 +22,7 @@ import org.apache.catalina.connector.Connector;
|
|||||||
import org.apache.catalina.startup.Tomcat;
|
import org.apache.catalina.startup.Tomcat;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||||
import org.apache.tomcat.util.net.SSLHostConfig;
|
import org.apache.tomcat.util.net.SSLHostConfig;
|
||||||
import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
|
import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
@@ -157,6 +158,47 @@ class SslConnectorCustomizerTests {
|
|||||||
assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2");
|
assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithPackageResources("test.jks")
|
||||||
|
void updateRetainsCustomizationsAppliedToSslHostConfig() {
|
||||||
|
Ssl ssl = new Ssl();
|
||||||
|
ssl.setKeyPassword("password");
|
||||||
|
ssl.setKeyStore("classpath:test.jks");
|
||||||
|
Connector connector = this.tomcat.getConnector();
|
||||||
|
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
|
||||||
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||||
|
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||||
|
SSLHostConfig sslHostConfig = protocol.findSslHostConfigs()[0];
|
||||||
|
sslHostConfig.setTruststoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||||
|
customizer.update(null, WebServerSslBundle.get(ssl));
|
||||||
|
assertThat(protocol.findSslHostConfigs()).hasSize(1);
|
||||||
|
SSLHostConfig updated = protocol.findSslHostConfigs()[0];
|
||||||
|
assertThat(updated.getTruststoreProvider()).isEqualTo(MockPkcs11SecurityProvider.NAME);
|
||||||
|
assertThat(updated.getCertificates()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithPackageResources("test.jks")
|
||||||
|
void updateAppliesUpdatedBundleToExistingSslHostConfig() throws Exception {
|
||||||
|
Ssl ssl = new Ssl();
|
||||||
|
ssl.setKeyPassword("password");
|
||||||
|
ssl.setKeyStore("classpath:test.jks");
|
||||||
|
ssl.setEnabledProtocols(new String[] { "TLSv1.2" });
|
||||||
|
Connector connector = this.tomcat.getConnector();
|
||||||
|
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
|
||||||
|
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
|
||||||
|
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
|
||||||
|
this.tomcat.start();
|
||||||
|
assertThat(protocol.findSslHostConfigs()[0].getEnabledProtocols()).containsExactly("TLSv1.2");
|
||||||
|
Ssl updatedSsl = new Ssl();
|
||||||
|
updatedSsl.setKeyPassword("password");
|
||||||
|
updatedSsl.setKeyStore("classpath:test.jks");
|
||||||
|
updatedSsl.setEnabledProtocols(new String[] { "TLSv1.3" });
|
||||||
|
customizer.update(null, WebServerSslBundle.get(updatedSsl));
|
||||||
|
SSLHostConfig sslHostConfig = protocol.findSslHostConfigs()[0];
|
||||||
|
assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.3");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void customizeWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
|
void customizeWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
|
||||||
assertThatIllegalStateException().isThrownBy(() -> {
|
assertThatIllegalStateException().isThrownBy(() -> {
|
||||||
|
|||||||
+28
@@ -72,6 +72,7 @@ import org.apache.hc.core5.ssl.SSLContextBuilder;
|
|||||||
import org.apache.jasper.servlet.JspServlet;
|
import org.apache.jasper.servlet.JspServlet;
|
||||||
import org.apache.tomcat.JarScanFilter;
|
import org.apache.tomcat.JarScanFilter;
|
||||||
import org.apache.tomcat.JarScanType;
|
import org.apache.tomcat.JarScanType;
|
||||||
|
import org.apache.tomcat.util.net.SSLHostConfig;
|
||||||
import org.apache.tomcat.util.scan.StandardJarScanFilter;
|
import org.apache.tomcat.util.scan.StandardJarScanFilter;
|
||||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||||
import org.awaitility.Awaitility;
|
import org.awaitility.Awaitility;
|
||||||
@@ -700,6 +701,33 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory
|
|||||||
assertThat(verifier.getLastPrincipal()).isEqualTo("CN=2");
|
assertThat(verifier.getLastPrincipal()).isEqualTo("CN=2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithPackageResources({ "1.crt", "1.key", "2.crt", "2.key" })
|
||||||
|
void shouldRetainSslHostConfigCustomizationsWhenReloadingSslBundles() throws Exception {
|
||||||
|
TomcatServletWebServerFactory factory = getFactory();
|
||||||
|
addTestTxtFile(factory);
|
||||||
|
DefaultSslBundleRegistry bundles = new DefaultSslBundleRegistry("test",
|
||||||
|
createPemSslBundle("classpath:1.crt", "classpath:1.key"));
|
||||||
|
factory.setSslBundles(bundles);
|
||||||
|
factory.setSsl(Ssl.forBundle("test"));
|
||||||
|
factory.addConnectorCustomizers((connector) -> {
|
||||||
|
if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol<?> protocol) {
|
||||||
|
for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
|
||||||
|
sslHostConfig.setSessionTimeout(12345);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.webServer = factory.getWebServer();
|
||||||
|
this.webServer.start();
|
||||||
|
bundles.updateBundle("test", createPemSslBundle("classpath:2.crt", "classpath:2.key"));
|
||||||
|
Connector connector = ((TomcatWebServer) this.webServer).getTomcat().getConnector();
|
||||||
|
AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) connector.getProtocolHandler();
|
||||||
|
assertThat(protocol.findSslHostConfigs()).hasSize(1);
|
||||||
|
SSLHostConfig sslHostConfig = protocol.findSslHostConfigs()[0];
|
||||||
|
assertThat(sslHostConfig.getSessionTimeout()).isEqualTo(12345);
|
||||||
|
assertThat(sslHostConfig.getCertificates()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithPackageResources("test.jks")
|
@WithPackageResources("test.jks")
|
||||||
void sslWithHttp11Nio2Protocol() throws Exception {
|
void sslWithHttp11Nio2Protocol() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user