From 00f88b9e0582845ef9a4c704bf3ec7889b1bdb74 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 21 Jan 2025 11:24:58 +0000 Subject: [PATCH] Remove overly specific casts from SslConnectorCustomizer Closes gh-43849 --- .../tomcat/SslConnectorCustomizer.java | 21 ++-- .../build.gradle | 21 ++++ .../ssl/SampleTomcat11SslApplication.java | 29 ++++++ .../tomcat/ssl/web/SampleController.java | 30 ++++++ .../src/main/resources/application.properties | 13 +++ .../src/main/resources/sample.jks | Bin 0 -> 2264 bytes .../SampleTomcat11SslApplicationTests.java | 92 ++++++++++++++++++ 7 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/build.gradle create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/SampleTomcat11SslApplication.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/web/SampleController.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/application.properties create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/sample.jks create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcat11SslApplicationTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java index ea49d05dc75..cadb12d4242 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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,7 +21,7 @@ import java.util.Map; import org.apache.catalina.connector.Connector; import org.apache.commons.logging.Log; import org.apache.coyote.ProtocolHandler; -import org.apache.coyote.http11.AbstractHttp11JsseProtocol; +import org.apache.coyote.http11.AbstractHttp11Protocol; import org.apache.tomcat.util.net.SSLHostConfig; import org.apache.tomcat.util.net.SSLHostConfigCertificate; import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type; @@ -58,7 +58,7 @@ class SslConnectorCustomizer { } void update(String serverName, SslBundle updatedSslBundle) { - AbstractHttp11JsseProtocol protocol = (AbstractHttp11JsseProtocol) this.connector.getProtocolHandler(); + AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) this.connector.getProtocolHandler(); String host = (serverName != null) ? serverName : protocol.getDefaultSSLHostConfigName(); this.logger.debug("SSL Bundle for host " + host + " has been updated, reloading SSL configuration"); addSslHostConfig(protocol, host, updatedSslBundle); @@ -66,20 +66,20 @@ class SslConnectorCustomizer { void customize(SslBundle sslBundle, Map serverNameSslBundles) { ProtocolHandler handler = this.connector.getProtocolHandler(); - Assert.state(handler instanceof AbstractHttp11JsseProtocol, - "To use SSL, the connector's protocol handler must be an AbstractHttp11JsseProtocol subclass"); - configureSsl((AbstractHttp11JsseProtocol) handler, sslBundle, serverNameSslBundles); + Assert.state(handler instanceof AbstractHttp11Protocol, + "To use SSL, the connector's protocol handler must be an AbstractHttp11Protocol subclass"); + configureSsl((AbstractHttp11Protocol) handler, sslBundle, serverNameSslBundles); this.connector.setScheme("https"); this.connector.setSecure(true); } /** - * Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL. + * Configure Tomcat's {@link AbstractHttp11Protocol} for SSL. * @param protocol the protocol * @param sslBundle the SSL bundle * @param serverNameSslBundles the SSL bundles for specific SNI host names */ - private void configureSsl(AbstractHttp11JsseProtocol protocol, SslBundle sslBundle, + private void configureSsl(AbstractHttp11Protocol protocol, SslBundle sslBundle, Map serverNameSslBundles) { protocol.setSSLEnabled(true); if (sslBundle != null) { @@ -88,7 +88,7 @@ class SslConnectorCustomizer { serverNameSslBundles.forEach((serverName, bundle) -> addSslHostConfig(protocol, serverName, bundle)); } - private void addSslHostConfig(AbstractHttp11JsseProtocol protocol, String serverName, SslBundle sslBundle) { + private void addSslHostConfig(AbstractHttp11Protocol protocol, String serverName, SslBundle sslBundle) { SSLHostConfig sslHostConfig = new SSLHostConfig(); sslHostConfig.setHostName(serverName); configureSslClientAuth(sslHostConfig); @@ -96,8 +96,7 @@ class SslConnectorCustomizer { protocol.addSslHostConfig(sslHostConfig, true); } - private void applySslBundle(AbstractHttp11JsseProtocol protocol, SSLHostConfig sslHostConfig, - SslBundle sslBundle) { + private void applySslBundle(AbstractHttp11Protocol protocol, SSLHostConfig sslHostConfig, SslBundle sslBundle) { SslBundleKey key = sslBundle.getKey(); SslStoreBundle stores = sslBundle.getStores(); SslOptions options = sslBundle.getOptions(); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/build.gradle new file mode 100644 index 00000000000..7744f64216a --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/build.gradle @@ -0,0 +1,21 @@ +plugins { + id "java" +} + +description = "Spring Boot Tomcat 11 SSL smoke test" + +configurations.all { + resolutionStrategy.eachDependency { + if (it.requested.group == 'org.apache.tomcat' || it.requested.group == 'org.apache.tomcat.embed') { + it.useVersion '11.0.0' + } + } +} + +dependencies { + implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) + implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator")) + + testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) + testImplementation("org.apache.httpcomponents.client5:httpclient5") +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/SampleTomcat11SslApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/SampleTomcat11SslApplication.java new file mode 100644 index 00000000000..26a66f7146e --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/SampleTomcat11SslApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2025 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 smoketest.tomcat.ssl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleTomcat11SslApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleTomcat11SslApplication.class, args); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/web/SampleController.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/web/SampleController.java new file mode 100644 index 00000000000..56f8ba7d702 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/java/smoketest/tomcat/ssl/web/SampleController.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2025 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 smoketest.tomcat.ssl.web; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SampleController { + + @GetMapping("/") + public String helloWorld() { + return "Hello, world"; + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/application.properties new file mode 100644 index 00000000000..c9f855cfcee --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/application.properties @@ -0,0 +1,13 @@ +server.port=8443 + +management.endpoints.web.exposure.include=* +management.endpoint.health.show-details=always +management.health.ssl.certificate-validity-warning-threshold=7d +management.health.ssl.enabled=true +management.info.ssl.enabled=true + +server.ssl.bundle=ssldemo +spring.ssl.bundle.jks.ssldemo.keystore.location=classpath:sample.jks +spring.ssl.bundle.jks.ssldemo.keystore.password=secret +spring.ssl.bundle.jks.ssldemo.keystore.type=JKS +spring.ssl.bundle.jks.ssldemo.key.password=password diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/sample.jks b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/main/resources/sample.jks new file mode 100644 index 0000000000000000000000000000000000000000..6aa9a28053a591e41453e665e5024e8a8cb78b3d GIT binary patch literal 2264 zcmchYX*3iJ7sqE|hQS!q5Mv)4GM2$i#uAFqC`%7x7baWA*i&dRX>3`uq(XS?3XSYp z%38`&ib7E$8j~$cF^}gt?|I+noW8#w?uYxk=iGD8|K9Vzd#pVc0002(2k@T|2@MMI zqxqr2AhQO*TVi`j@((S;e;g;l$#dAA{>vf0kX$R(Qn4oKgGEYjZ5zti2dw?Z6A zh%LuFCNI?9o+Z1duJL-++e#cjO`zlK?u9s030=k_*wD1#-$FbIDRDnA^vo@fm( zzjt(3VJrGOr0iHXSTM|rYN#>RZ@Dp`PwB2zrDQffLvuoR2~V3ReYa0&vU^dXd8isV zsAf*@!8s%xBvHLseXn6f?1kefe(8uAmAbaF$x{Ykzb6c6jdUwY1$y4tFzsj7 zIghr!T#ODfu@Po!a29@kXQ8kY#(LE<0o7?7PQ|eMeY@Equ?R-6*f@Na3o&stDQ=6( zQzDSQhCnS(9Bu9W_~giknP0vECqUsr4_9y_}nEU`cy z4}dApnAip92wMwgzciAFpc3i}+-#Zlq+iF7d1y}d4Qsp8=%l1N8NIs161I`HmkcpQ zY4*CUCFJJf(2!M{`&qQ}3($KeTQ=)mMrBs`DOb;%Of0tC)9he_p~w&CO#DfCgx(%s z{@|D(brX_Gb}ZDLmGej*JgEl0Et>q~kgTXuJg-PwvRjNx8sBbIShxD=xOySzw{;^X zAvrh5HTg>Xq@<{#^!Kg}B?qz@b<{ebD)yaSf&RChBIJQo-?Ahzw@qopSe^e&>^IuU zydM4Y1_C&>k7u|}=; z63R7$H6zat=hNExxEwXu1fQ*ytuEkP!{w{|#6TIEq1#*ck=6_NM*ILF65tmD-O5&R zMI!-MT<3U~t@}(CN4@RlZ~1I>C=!ywF)dNI{VvH;5Y3(Z4jY^%_c&fsm4Q`<1g|qX z&!h29jXjVE3nJnet*L)XL?-8<>qDbVGP%i^NwOZfwWO7?Mr!X7 zl}sG@9S_5}}td}$xrWIYY=e(VVBiv%A+M-{M z!3_^Tc=pV?niT!{D`!{e@W;MvrZ(OER{x7itVAtwE~spPtPtma|J=5dv&_oE!5H#` zdgXJ;+gJ4hI}*9QX9jpL`Gb)yCe%1}t!&O-^sihyZys%%5uF~WhsR_w(q7;vV5d4P zr%ZUA2}kO+L^2ePTgGT9Ua71w<+)poSyjTdLq&xbUn`<6&SpwFp(HRHUyU6J3WZ_! zfztko79+94Tq%mTYj53(RYcL&1~5`I#+w3`(Q|r+P(aT z%?r(^?IWw~19CB&uvXf(f7&BnEE{zwK4piVU`I4j1j?v5d4N<7VUJ8nM`$7S*mfKR z#9-JzPRZ?{M!@L+0N^V)IyeeP2T|^UK|m0QD+Ibs!wEoml^N!YO#vW~j~jraX(0A3 z6Kux?IRLez`O^X;{!4g%BhcRn>^H*qKZ3*|{_YGuz)KCJcu;)DSES5D2tDE`C02YR0R%Vy1T7k|RQ;3g<0icA$AuP0pOvc~jGl zz+NeKv_FT_;GWK&8XlDUv&hv9kxg?@c!bu?83i=YQ$S!K09Y)Glg3Hz?@|)ZCBlVz zP8i}#XZkMoje3I=h&I!!s_m?Qi@1MR`yv7X*yEs47qOs^t^?&=;*IQ!q&)gq_Sx5* z?fhU8Q*PSe*w7y)FH#P!9R^Xw!lTT+zI39L<&8cViaj$A(Z2Cg7!{V?uuyi#vlNCg z40i}2ivw&y&1-&Nh&WMG`&aIt>)(#tKTJ}^@696Kw1-{IzSOTnFF+0@k$o3%ZHS;Q#;t literal 0 HcmV?d00001 diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcat11SslApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcat11SslApplicationTests.java new file mode 100644 index 00000000000..0c671c68455 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat11-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcat11SslApplicationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2025 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 smoketest.tomcat.ssl; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.json.JsonContent; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class SampleTomcat11SslApplicationTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private AbstractConfigurableWebServerFactory webServerFactory; + + @Test + void testSsl() { + assertThat(this.webServerFactory.getSsl().isEnabled()).isTrue(); + } + + @Test + void testHome() { + ResponseEntity entity = this.restTemplate.getForEntity("/", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).isEqualTo("Hello, world"); + } + + @Test + void testSslInfo() { + ResponseEntity entity = this.restTemplate.getForEntity("/actuator/info", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + JsonContent body = new JsonContent(entity.getBody()); + assertThat(body).extractingPath("ssl.bundles[0].name").isEqualTo("ssldemo"); + assertThat(body).extractingPath("ssl.bundles[0].certificateChains[0].alias") + .isEqualTo("spring-boot-ssl-sample"); + assertThat(body).extractingPath("ssl.bundles[0].certificateChains[0].certificates[0].issuer") + .isEqualTo("CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown"); + assertThat(body).extractingPath("ssl.bundles[0].certificateChains[0].certificates[0].subject") + .isEqualTo("CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown"); + assertThat(body).extractingPath("ssl.bundles[0].certificateChains[0].certificates[0].validity.status") + .isEqualTo("EXPIRED"); + assertThat(body).extractingPath("ssl.bundles[0].certificateChains[0].certificates[0].validity.message") + .asString() + .startsWith("Not valid after "); + } + + @Test + void testSslHealth() { + ResponseEntity entity = this.restTemplate.getForEntity("/actuator/health", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); + JsonContent body = new JsonContent(entity.getBody()); + assertThat(body).extractingPath("status").isEqualTo("OUT_OF_SERVICE"); + assertThat(body).extractingPath("components.ssl.status").isEqualTo("OUT_OF_SERVICE"); + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].alias") + .isEqualTo("spring-boot-ssl-sample"); + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].issuer") + .isEqualTo("CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown"); + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].subject") + .isEqualTo("CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown"); + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].validity.status") + .isEqualTo("EXPIRED"); + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].validity.message") + .asString() + .startsWith("Not valid after "); + } + +}