From 580b1b81ab2548b070113b88bda55a1ec627bde0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 21 Jun 2021 12:06:10 +0100 Subject: [PATCH] Fix HTTP/2 over TLS with Jetty 10 Fixes gh-26988 --- .../embedded/jetty/SslServerCustomizer.java | 19 ++--- .../build.gradle | 18 +++++ .../src/main/resources/sample.jks | Bin 0 -> 2264 bytes .../jetty10/Jetty10Http2OverTlsTests.java | 69 ++++++++++++++++++ 4 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/sample.jks create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java index d9f57b33ff..d0648c66b5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java @@ -106,24 +106,25 @@ class SslServerCustomizer implements JettyServerCustomizer { private ServerConnector createHttp11ServerConnector(Server server, HttpConfiguration config, SslContextFactory.Server sslContextFactory) { HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config); - SslConnectionFactory sslConnectionFactory; + return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), + createSslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), connectionFactory); + } + + private SslConnectionFactory createSslConnectionFactory(SslContextFactory.Server sslContextFactory, + String protocol) { try { - sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()); + return new SslConnectionFactory(sslContextFactory, protocol); } catch (NoSuchMethodError ex) { // Jetty 10 try { - sslConnectionFactory = SslConnectionFactory.class - .getConstructor(SslContextFactory.Server.class, String.class) - .newInstance(sslContextFactory, HttpVersion.HTTP_1_1.asString()); + return SslConnectionFactory.class.getConstructor(SslContextFactory.Server.class, String.class) + .newInstance(sslContextFactory, protocol); } catch (Exception ex2) { throw new RuntimeException(ex2); } } - - return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), sslConnectionFactory, - connectionFactory); } private boolean isJettyAlpnPresent() { @@ -143,7 +144,7 @@ class SslServerCustomizer implements JettyServerCustomizer { if (isConscryptPresent()) { sslContextFactory.setProvider("Conscrypt"); } - SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); + SslConnectionFactory ssl = createSslConnectionFactory(sslContextFactory, alpn.getProtocol()); return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), ssl, alpn, h2, http); } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle index a8b3171a87..2591ecd3fc 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/build.gradle @@ -14,6 +14,24 @@ dependencies { implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) { exclude module: "spring-boot-starter-tomcat" } + + runtimeOnly("org.eclipse.jetty:jetty-alpn-java-server") + runtimeOnly("org.eclipse.jetty.http2:http2-server") testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) + testImplementation("org.eclipse.jetty:jetty-client") + testImplementation("org.eclipse.jetty.http2:http2-client") + testImplementation("org.eclipse.jetty.http2:http2-http-client-transport") +} + +def buildingWithJava11OrLater() { + return project.hasProperty("toolchainVersion") || JavaVersion.current().java11Compatible +} + +compileTestJava { + enabled = buildingWithJava11OrLater() +} + +test { + enabled = buildingWithJava11OrLater() } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/main/resources/sample.jks b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/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-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java new file mode 100644 index 0000000000..40114a9a5f --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-jetty10/src/test/java/smoketest/jetty10/Jetty10Http2OverTlsTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2021 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.jetty10; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for HTTP/2 over TLS (h2) with Jetty 10. + * + * @author Andy Wilkinson + */ +@EnabledForJreRange(min = JRE.JAVA_11) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, + properties = { "server.http2.enabled=true", "server.ssl.enabled=true", + "server.ssl.keystore=classpath:sample.jks", "server.ssl.key-store-password=secret", + "server.ssl.key-password=password", "logging.level.org.eclipse.jetty=debug" }) +public class Jetty10Http2OverTlsTests { + + @LocalServerPort + private int port; + + @Test + void httpOverTlsGetWhenHttp2AndSslAreEnabledSucceeds() throws Exception { + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + sslContextFactory.setTrustAll(true); + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setSslContextFactory(sslContextFactory); + HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client(clientConnector))); + client.start(); + try { + ContentResponse response = client.GET("https://localhost:" + this.port + "/"); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(response.getContentAsString()).isEqualTo("Hello World"); + } + finally { + client.stop(); + } + } + +}