From 4ca83049595f28e957f34159ca0f2b47255bf3aa Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Thu, 6 Aug 2026 14:18:40 -0500 Subject: [PATCH] Retain SSL host config customizations during Tomcat initialization Applying an SSL bundle to a Tomcat server replaced any `SSLHostConfig` already registered on the connector for a host name, so an `SSLHostConfig` could not be configured before Spring Boot applied a bundle to it. Reuse any existing `SSLHostConfig` during initial configuration, so an `SSLHostConfig` registered on the connector before the bundle is applied retains settings such as `trustManagerClassName`, `truststoreProvider`, or `truststoreAlgorithm`. Client authentication settings are only applied when creating a new `SSLHostConfig`, so the `certificateVerification` of an existing `SSLHostConfig` is retained rather than being overwritten with the value derived from the `server.ssl.client-auth` property. See gh-51466 Signed-off-by: Scott Frederick --- .../boot/tomcat/SslConnectorCustomizer.java | 17 ++++----- .../tomcat/SslConnectorCustomizerTests.java | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/SslConnectorCustomizer.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/SslConnectorCustomizer.java index b947e018391..ef3433a0646 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/SslConnectorCustomizer.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/SslConnectorCustomizer.java @@ -68,13 +68,7 @@ public class SslConnectorCustomizer { AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) this.connector.getProtocolHandler(); String hostName = (serverName != null) ? serverName : protocol.getDefaultSSLHostConfigName(); this.logger.debug("SSL Bundle for host " + hostName + " has been updated, reloading SSL configuration"); - SSLHostConfig sslHostConfig = findSslHostConfig(protocol, hostName); - if (sslHostConfig == null) { - addSslHostConfig(protocol, hostName, updatedSslBundle); - return; - } - applySslBundle(protocol, sslHostConfig, updatedSslBundle); - protocol.addSslHostConfig(sslHostConfig, true); + addSslHostConfig(protocol, hostName, updatedSslBundle); } public void customize(SslBundle sslBundle, Map serverNameSslBundles) { @@ -102,9 +96,12 @@ public class SslConnectorCustomizer { } private void addSslHostConfig(AbstractHttp11Protocol protocol, String hostName, SslBundle sslBundle) { - SSLHostConfig sslHostConfig = new SSLHostConfig(); - sslHostConfig.setHostName(hostName); - configureSslClientAuth(sslHostConfig); + SSLHostConfig sslHostConfig = findSslHostConfig(protocol, hostName); + if (sslHostConfig == null) { + sslHostConfig = new SSLHostConfig(); + sslHostConfig.setHostName(hostName); + configureSslClientAuth(sslHostConfig); + } applySslBundle(protocol, sslHostConfig, sslBundle); protocol.addSslHostConfig(sslHostConfig, true); } diff --git a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/SslConnectorCustomizerTests.java b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/SslConnectorCustomizerTests.java index d362b4e1d5c..a7e39d3b7b1 100644 --- a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/SslConnectorCustomizerTests.java +++ b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/SslConnectorCustomizerTests.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.Log; 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.CertificateVerification; import org.apache.tomcat.util.net.openssl.ciphers.Cipher; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -158,6 +159,26 @@ class SslConnectorCustomizerTests { assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2"); } + @Test + @WithPackageResources("test.jks") + void customizeRetainsCustomizationsAppliedToExistingSslHostConfig() { + Ssl ssl = new Ssl(); + ssl.setKeyPassword("password"); + ssl.setKeyStore("classpath:test.jks"); + Connector connector = this.tomcat.getConnector(); + AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) connector.getProtocolHandler(); + SSLHostConfig existing = new SSLHostConfig(); + existing.setHostName(protocol.getDefaultSSLHostConfigName()); + existing.setTruststoreProvider(MockPkcs11SecurityProvider.NAME); + protocol.addSslHostConfig(existing); + SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth()); + customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap()); + assertThat(protocol.findSslHostConfigs()).hasSize(1); + SSLHostConfig sslHostConfig = protocol.findSslHostConfigs()[0]; + assertThat(sslHostConfig.getTruststoreProvider()).isEqualTo(MockPkcs11SecurityProvider.NAME); + assertThat(sslHostConfig.getCertificates()).hasSize(1); + } + @Test @WithPackageResources("test.jks") void updateRetainsCustomizationsAppliedToSslHostConfig() { @@ -177,6 +198,23 @@ class SslConnectorCustomizerTests { assertThat(updated.getCertificates()).hasSize(1); } + @Test + @WithPackageResources("test.jks") + void updateRetainsClientAuthConfiguredOnSslHostConfig() { + 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.setCertificateVerification("required"); + customizer.update(null, WebServerSslBundle.get(ssl)); + SSLHostConfig updated = protocol.findSslHostConfigs()[0]; + assertThat(updated.getCertificateVerification()).isEqualTo(CertificateVerification.REQUIRED); + } + @Test @WithPackageResources("test.jks") void updateAppliesUpdatedBundleToExistingSslHostConfig() throws Exception {