Upgrade to Tomcat 8.5.3

This commit changes the default version of Tomcat to 8.5.3 while
also retaining support for Tomcat 8.0 and 7.0. The main difference
in 8.5 is that the ServerSocketFactory abstraction that allowed the
TrustStore and KeyStore to be configured programatically no longer
exists. This logic has been replaced with the use of a custom URL
protocol (springbootssl) that provides access to the key store and
trust store of an SslStoreProvider. In addition to working with 8.5,
this approach has the advantage of also working with 8.0 and 7.0.

Closes gh-6164
This commit is contained in:
Andy Wilkinson 2016-06-17 20:42:33 +01:00
parent 06b81cf16f
commit f28e3d54c5
28 changed files with 521 additions and 154 deletions

View File

@ -308,8 +308,8 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-juli</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -173,7 +173,7 @@
<thymeleaf-layout-dialect.version>1.4.0</thymeleaf-layout-dialect.version> <thymeleaf-layout-dialect.version>1.4.0</thymeleaf-layout-dialect.version>
<thymeleaf-extras-data-attribute.version>1.3</thymeleaf-extras-data-attribute.version> <thymeleaf-extras-data-attribute.version>1.3</thymeleaf-extras-data-attribute.version>
<thymeleaf-extras-java8time.version>2.1.0.RELEASE</thymeleaf-extras-java8time.version> <thymeleaf-extras-java8time.version>2.1.0.RELEASE</thymeleaf-extras-java8time.version>
<tomcat.version>8.0.33</tomcat.version> <tomcat.version>8.5.3</tomcat.version>
<undertow.version>1.3.22.Final</undertow.version> <undertow.version>1.3.22.Final</undertow.version>
<velocity.version>1.7</velocity.version> <velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.version> <velocity-tools.version>2.0</velocity-tools.version>
@ -1314,11 +1314,6 @@
<artifactId>tomcat-embed-jasper</artifactId> <artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version> <version>${tomcat.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId> <artifactId>tomcat-embed-websocket</artifactId>
@ -1334,6 +1329,11 @@
<artifactId>tomcat-jsp-api</artifactId> <artifactId>tomcat-jsp-api</artifactId>
<version>${tomcat.version}</version> <version>${tomcat.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId> <artifactId>velocity</artifactId>

View File

@ -118,8 +118,8 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-juli</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -300,8 +300,8 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-juli</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -193,7 +193,7 @@ The following sample applications are provided:
| Embedded Tomcat | Embedded Tomcat
| link:spring-boot-sample-tomcat-jsp[spring-boot-sample-tomcat-jsp] | link:spring-boot-sample-tomcat-jsp[spring-boot-sample-tomcat-jsp]
| Web application that uses JSP templates with Tomcat 8 | Web application that uses JSP templates with Tomcat
| link:spring-boot-sample-tomcat-multi-connectors[spring-boot-sample-tomcat-multi-connectors] | link:spring-boot-sample-tomcat-multi-connectors[spring-boot-sample-tomcat-multi-connectors]
| Web application that uses Tomcat configured with multiple connectors | Web application that uses Tomcat configured with multiple connectors
@ -204,6 +204,12 @@ The following sample applications are provided:
| link:spring-boot-sample-tomcat7-jsp[spring-boot-sample-tomcat7-jsp] | link:spring-boot-sample-tomcat7-jsp[spring-boot-sample-tomcat7-jsp]
| Web application that uses JSP templates with Tomcat 7 | Web application that uses JSP templates with Tomcat 7
| link:spring-boot-sample-tomcat7-ssl[spring-boot-sample-tomcat7-ssl]
| Web application that uses Tomcat 7 configured with SSL
| link:spring-boot-sample-tomcat80-ssl[spring-boot-sample-tomcat80-ssl]
| Web application that uses Tomcat 8.0 configured with SSL
| link:spring-boot-sample-traditional[spring-boot-sample-traditional] | link:spring-boot-sample-traditional[spring-boot-sample-traditional]
| Traditional WAR packaging (but also executable using `java -jar`) | Traditional WAR packaging (but also executable using `java -jar`)

View File

@ -87,6 +87,8 @@
<module>spring-boot-sample-tomcat-ssl</module> <module>spring-boot-sample-tomcat-ssl</module>
<module>spring-boot-sample-tomcat-multi-connectors</module> <module>spring-boot-sample-tomcat-multi-connectors</module>
<module>spring-boot-sample-tomcat7-jsp</module> <module>spring-boot-sample-tomcat7-jsp</module>
<module>spring-boot-sample-tomcat7-ssl</module>
<module>spring-boot-sample-tomcat80-ssl</module>
<module>spring-boot-sample-traditional</module> <module>spring-boot-sample-traditional</module>
<module>spring-boot-sample-undertow</module> <module>spring-boot-sample-undertow</module>
<module>spring-boot-sample-undertow-ssl</module> <module>spring-boot-sample-undertow-ssl</module>

View File

@ -19,6 +19,7 @@
<properties> <properties>
<main.basedir>${basedir}/../..</main.basedir> <main.basedir>${basedir}/../..</main.basedir>
<m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot> <m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot>
<tomcat.version>7.0.69</tomcat.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>

View File

@ -8,7 +8,7 @@
<version>1.4.0.BUILD-SNAPSHOT</version> <version>1.4.0.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-sample-tomcat-ssl</artifactId> <artifactId>spring-boot-sample-tomcat-ssl</artifactId>
<name>Spring Boot Tomcat Sample</name> <name>Spring Boot Tomcat SSL Sample</name>
<description>Spring Boot Tomcat SSL Sample</description> <description>Spring Boot Tomcat SSL Sample</description>
<url>http://projects.spring.io/spring-boot/</url> <url>http://projects.spring.io/spring-boot/</url>
<organization> <organization>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-tomcat7-ssl</artifactId>
<name>Spring Boot Tomcat 7 SSL Sample</name>
<description>Spring Boot Tomcat 7 SSL Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<tomcat.version>7.0.69</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,29 @@
/*
* Copyright 2012-2015 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
*
* http://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 sample.tomcat.ssl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleTomcatSslApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleTomcatSslApplication.class, args);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2012-2016 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
*
* http://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 sample.tomcat.ssl.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping("/")
@ResponseBody
public String helloWorld() {
return "Hello, world";
}
}

View File

@ -0,0 +1,4 @@
server.port = 8443
server.ssl.key-store = classpath:sample.jks
server.ssl.key-store-password = secret
server.ssl.key-password = password

View File

@ -0,0 +1,51 @@
/*
* Copyright 2012-2016 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
*
* http://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 sample.tomcat.ssl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.context.embedded.LocalServerPort;
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.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
public class SampleTomcatSslApplicationTests {
@LocalServerPort
private int port;
@Test
public void testHome() throws Exception {
TestRestTemplate testRestTemplate = new TestRestTemplate(HttpClientOption.SSL);
ResponseEntity<String> entity = testRestTemplate
.getForEntity("https://localhost:" + this.port, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("Hello, world");
}
}

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-tomcat-ssl</artifactId>
<name>Spring Boot Tomcat 8.0 SSL Sample</name>
<description>Spring Boot Tomcat 8.0 SSL Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<tomcat.version>8.0.33</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,29 @@
/*
* Copyright 2012-2015 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
*
* http://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 sample.tomcat.ssl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleTomcatSslApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleTomcatSslApplication.class, args);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2012-2016 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
*
* http://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 sample.tomcat.ssl.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping("/")
@ResponseBody
public String helloWorld() {
return "Hello, world";
}
}

View File

@ -0,0 +1,4 @@
server.port = 8443
server.ssl.key-store = classpath:sample.jks
server.ssl.key-store-password = secret
server.ssl.key-password = password

View File

@ -0,0 +1,51 @@
/*
* Copyright 2012-2016 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
*
* http://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 sample.tomcat.ssl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.context.embedded.LocalServerPort;
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.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
public class SampleTomcatSslApplicationTests {
@LocalServerPort
private int port;
@Test
public void testHome() throws Exception {
TestRestTemplate testRestTemplate = new TestRestTemplate(HttpClientOption.SSL);
ResponseEntity<String> entity = testRestTemplate
.getForEntity("https://localhost:" + this.port, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("Hello, world");
}
}

View File

@ -28,8 +28,8 @@
<artifactId>tomcat-embed-el</artifactId> <artifactId>tomcat-embed-el</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-juli</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat.embed</groupId>

View File

@ -112,8 +112,8 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-juli</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -110,8 +110,8 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-juli</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -1,98 +0,0 @@
/*
* Copyright 2012-2016 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
*
* http://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.context.embedded.tomcat;
import java.io.IOException;
import java.security.KeyStore;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.SSLUtil;
import org.apache.tomcat.util.net.ServerSocketFactory;
import org.apache.tomcat.util.net.jsse.JSSEImplementation;
import org.apache.tomcat.util.net.jsse.JSSESocketFactory;
import org.springframework.boot.context.embedded.SslStoreProvider;
/**
* {@link JSSEImplementation} for embedded Tomcat that supports {@link SslStoreProvider}.
*
* @author Phillip Webb
* @author Venil Noronha
* @since 1.4.0
*/
public class TomcatEmbeddedJSSEImplementation extends JSSEImplementation {
@Override
public ServerSocketFactory getServerSocketFactory(AbstractEndpoint<?> endpoint) {
return new SocketFactory(endpoint);
}
@Override
public SSLUtil getSSLUtil(AbstractEndpoint<?> endpoint) {
return new SocketFactory(endpoint);
}
/**
* {@link JSSESocketFactory} that supports {@link SslStoreProvider}.
*/
static class SocketFactory extends JSSESocketFactory {
private final SslStoreProvider sslStoreProvider;
SocketFactory(AbstractEndpoint<?> endpoint) {
super(endpoint);
this.sslStoreProvider = (SslStoreProvider) endpoint
.getAttribute("sslStoreProvider");
}
@Override
protected KeyStore getKeystore(String type, String provider, String pass)
throws IOException {
if (this.sslStoreProvider != null) {
try {
KeyStore store = this.sslStoreProvider.getKeyStore();
if (store != null) {
return store;
}
}
catch (Exception ex) {
throw new IOException(ex);
}
}
return super.getKeystore(type, provider, pass);
}
@Override
protected KeyStore getTrustStore(String keystoreType, String keystoreProvider)
throws IOException {
if (this.sslStoreProvider != null) {
try {
KeyStore store = this.sslStoreProvider.getTrustStore();
if (store != null) {
return store;
}
}
catch (Exception ex) {
throw new IOException(ex);
}
}
return super.getTrustStore(keystoreType, keystoreProvider);
}
}
}

View File

@ -16,12 +16,19 @@
package org.springframework.boot.context.embedded.tomcat; package org.springframework.boot.context.embedded.tomcat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.KeyStore;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -48,11 +55,13 @@ import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.session.StandardManager; import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener; import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.coyote.AbstractProtocol; import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler; import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol; import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol; import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.coyote.http11.Http11NioProtocol; import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
@ -301,7 +310,7 @@ public class TomcatEmbeddedServletContainerFactory
Compression compression = getCompression(); Compression compression = getCompression();
protocol.setCompression("on"); protocol.setCompression("on");
protocol.setCompressionMinSize(compression.getMinResponseSize()); protocol.setCompressionMinSize(compression.getMinResponseSize());
protocol.setCompressableMimeTypes( protocol.setCompressableMimeType(
StringUtils.arrayToCommaDelimitedString(compression.getMimeTypes())); StringUtils.arrayToCommaDelimitedString(compression.getMimeTypes()));
if (getCompression().getExcludedUserAgents() != null) { if (getCompression().getExcludedUserAgents() != null) {
protocol.setNoCompressionUserAgents( protocol.setNoCompressionUserAgents(
@ -323,13 +332,33 @@ public class TomcatEmbeddedServletContainerFactory
protocol.setKeystorePass(ssl.getKeyStorePassword()); protocol.setKeystorePass(ssl.getKeyStorePassword());
protocol.setKeyPass(ssl.getKeyPassword()); protocol.setKeyPass(ssl.getKeyPassword());
protocol.setKeyAlias(ssl.getKeyAlias()); protocol.setKeyAlias(ssl.getKeyAlias());
protocol.setCiphers(StringUtils.arrayToCommaDelimitedString(ssl.getCiphers())); String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
protocol.setCiphers(StringUtils.hasText(ciphers) ? ciphers : null);
if (ssl.getEnabledProtocols() != null) { if (ssl.getEnabledProtocols() != null) {
protocol.setProperty("sslEnabledProtocols", try {
StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols())); for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
sslHostConfig.setProtocols(StringUtils
.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
}
}
catch (NoSuchMethodError ex) {
// Tomcat 8.0.x or earlier
Assert.isTrue(
protocol.setProperty("sslEnabledProtocols",
StringUtils.arrayToCommaDelimitedString(
ssl.getEnabledProtocols())),
"Failed to set sslEnabledProtocols");
}
} }
if (getSslStoreProvider() != null) { if (getSslStoreProvider() != null) {
configureSslStoreProvider(protocol, getSslStoreProvider()); TomcatURLStreamHandlerFactory instance = TomcatURLStreamHandlerFactory
.getInstance();
instance.addUserFactory(
new SslStoreProviderUrlStreamHandlerFactory(getSslStoreProvider()));
protocol.setKeystoreFile(
SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL);
protocol.setTruststoreFile(
SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL);
} }
else { else {
configureSslKeyStore(protocol, ssl); configureSslKeyStore(protocol, ssl);
@ -350,10 +379,6 @@ public class TomcatEmbeddedServletContainerFactory
SslStoreProvider sslStoreProvider) { SslStoreProvider sslStoreProvider) {
Assert.isInstanceOf(Http11NioProtocol.class, protocol, Assert.isInstanceOf(Http11NioProtocol.class, protocol,
"SslStoreProvider can only be used with Http11NioProtocol"); "SslStoreProvider can only be used with Http11NioProtocol");
((Http11NioProtocol) protocol).getEndpoint().setAttribute("sslStoreProvider",
sslStoreProvider);
protocol.setSslImplementationName(
TomcatEmbeddedJSSEImplementation.class.getName());
} }
private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) { private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
@ -677,6 +702,89 @@ public class TomcatEmbeddedServletContainerFactory
return this.uriEncoding; return this.uriEncoding;
} }
/**
* A {@link URLStreamHandlerFactory} that provides a {@link URLStreamHandler} for
* accessing an {@link SslStoreProvider}'s key store and trust store from a URL.
*/
private static final class SslStoreProviderUrlStreamHandlerFactory
implements URLStreamHandlerFactory {
private static final String PROTOCOL = "springbootssl";
private static final String KEY_STORE_PATH = "keyStore";
private static final String KEY_STORE_URL = PROTOCOL + ":" + KEY_STORE_PATH;
private static final String TRUST_STORE_PATH = "trustStore";
private static final String TRUST_STORE_URL = PROTOCOL + ":" + TRUST_STORE_PATH;
private final SslStoreProvider sslStoreProvider;
private SslStoreProviderUrlStreamHandlerFactory(
SslStoreProvider sslStoreProvider) {
this.sslStoreProvider = sslStoreProvider;
}
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if (PROTOCOL.equals(protocol)) {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) throws IOException {
try {
if (KEY_STORE_PATH.equals(url.getPath())) {
return new KeyStoreUrlConnection(url,
SslStoreProviderUrlStreamHandlerFactory.this.sslStoreProvider
.getKeyStore());
}
if (TRUST_STORE_PATH.equals(url.getPath())) {
return new KeyStoreUrlConnection(url,
SslStoreProviderUrlStreamHandlerFactory.this.sslStoreProvider
.getTrustStore());
}
}
catch (Exception ex) {
throw new IOException(ex);
}
throw new IOException("Invalid path: " + url.getPath());
}
};
}
return null;
}
private static final class KeyStoreUrlConnection extends URLConnection {
private final KeyStore keyStore;
private KeyStoreUrlConnection(URL url, KeyStore keyStore) {
super(url);
this.keyStore = keyStore;
}
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
this.keyStore.store(stream, new char[0]);
return new ByteArrayInputStream(stream.toByteArray());
}
catch (Exception ex) {
throw new IOException(ex);
}
}
}
}
private static class TomcatErrorPage { private static class TomcatErrorPage {
private static final String ERROR_PAGE_CLASS = "org.apache.tomcat.util.descriptor.web.ErrorPage"; private static final String ERROR_PAGE_CLASS = "org.apache.tomcat.util.descriptor.web.ErrorPage";
@ -752,8 +860,7 @@ public class TomcatEmbeddedServletContainerFactory
*/ */
private static class StoreMergedWebXmlListener implements LifecycleListener { private static class StoreMergedWebXmlListener implements LifecycleListener {
@SuppressWarnings("deprecation") private static final String MERGED_WEB_XML = "org.apache.tomcat.util.scan.MergedWebXml";
private final String MERGED_WEB_XML = org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML;
@Override @Override
public void lifecycleEvent(LifecycleEvent event) { public void lifecycleEvent(LifecycleEvent event) {
@ -764,8 +871,10 @@ public class TomcatEmbeddedServletContainerFactory
private void onStart(Context context) { private void onStart(Context context) {
ServletContext servletContext = context.getServletContext(); ServletContext servletContext = context.getServletContext();
if (servletContext.getAttribute(this.MERGED_WEB_XML) == null) { if (servletContext
servletContext.setAttribute(this.MERGED_WEB_XML, getEmptyWebXml()); .getAttribute(StoreMergedWebXmlListener.MERGED_WEB_XML) == null) {
servletContext.setAttribute(StoreMergedWebXmlListener.MERGED_WEB_XML,
getEmptyWebXml());
} }
TomcatResources.get(context).addClasspathResources(); TomcatResources.get(context).addClasspathResources();
} }

View File

@ -40,7 +40,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
@ -971,8 +971,8 @@ public class SpringApplicationTests {
static class ExampleWebConfig { static class ExampleWebConfig {
@Bean @Bean
public JettyEmbeddedServletContainerFactory container() { public TomcatEmbeddedServletContainerFactory container() {
return new JettyEmbeddedServletContainerFactory(0); return new TomcatEmbeddedServletContainerFactory(0);
} }
} }

View File

@ -55,6 +55,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.InputStreamFactory; import org.apache.http.client.entity.InputStreamFactory;
import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.protocol.HttpClientContext;
@ -124,6 +125,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
@BeforeClass @BeforeClass
@AfterClass @AfterClass
public static void uninstallUrlStreamHandlerFactory() { public static void uninstallUrlStreamHandlerFactory() {
ReflectionTestUtils.setField(TomcatURLStreamHandlerFactory.class, "instance",
null);
ReflectionTestUtils.setField(URL.class, "factory", null); ReflectionTestUtils.setField(URL.class, "factory", null);
} }
@ -589,7 +592,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
} }
private Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyStore) { protected Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyStore) {
return getSsl(clientAuth, keyPassword, keyStore, null, null, null); return getSsl(clientAuth, keyPassword, keyStore, null, null, null);
} }

View File

@ -38,7 +38,7 @@ import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector; import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.RemoteIpValve; import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol; import org.apache.tomcat.util.net.SSLHostConfig;
import org.junit.After; import org.junit.After;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -266,16 +266,14 @@ public class TomcatEmbeddedServletContainerFactoryTests
Tomcat tomcat = getTomcat(factory); Tomcat tomcat = getTomcat(factory);
Connector connector = tomcat.getConnector(); Connector connector = tomcat.getConnector();
AbstractHttp11JsseProtocol<?> jsseProtocol = (AbstractHttp11JsseProtocol<?>) connector SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler()
.getProtocolHandler(); .findSslHostConfigs();
assertThat(jsseProtocol.getCiphers()).isEqualTo("ALPHA,BRAVO,CHARLIE"); assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE");
} }
@Test @Test
public void sslEnabledMultipleProtocolsConfiguration() throws Exception { public void sslEnabledMultipleProtocolsConfiguration() throws Exception {
Ssl ssl = new Ssl(); Ssl ssl = getSsl(null, "password", "src/test/resources/test.jks");
ssl.setKeyStore("test.jks");
ssl.setKeyStorePassword("secret");
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" }); ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" }); ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
@ -284,21 +282,20 @@ public class TomcatEmbeddedServletContainerFactoryTests
this.container = factory this.container = factory
.getEmbeddedServletContainer(sessionServletRegistration()); .getEmbeddedServletContainer(sessionServletRegistration());
this.container.start();
Tomcat tomcat = ((TomcatEmbeddedServletContainer) this.container).getTomcat(); Tomcat tomcat = ((TomcatEmbeddedServletContainer) this.container).getTomcat();
Connector connector = tomcat.getConnector(); Connector connector = tomcat.getConnector();
AbstractHttp11JsseProtocol<?> jsseProtocol = (AbstractHttp11JsseProtocol<?>) connector SSLHostConfig sslHostConfig = connector.getProtocolHandler()
.getProtocolHandler(); .findSslHostConfigs()[0];
assertThat(jsseProtocol.getSslProtocol()).isEqualTo("TLS"); assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
assertThat(jsseProtocol.getProperty("sslEnabledProtocols")) assertThat(sslHostConfig.getEnabledProtocols())
.isEqualTo("TLSv1.1,TLSv1.2"); .containsExactlyInAnyOrder("TLSv1.1", "TLSv1.2");
} }
@Test @Test
public void sslEnabledProtocolsConfiguration() throws Exception { public void sslEnabledProtocolsConfiguration() throws Exception {
Ssl ssl = new Ssl(); Ssl ssl = getSsl(null, "password", "src/test/resources/test.jks");
ssl.setKeyStore("test.jks");
ssl.setKeyStorePassword("secret");
ssl.setEnabledProtocols(new String[] { "TLSv1.2" }); ssl.setEnabledProtocols(new String[] { "TLSv1.2" });
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" }); ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
@ -310,10 +307,11 @@ public class TomcatEmbeddedServletContainerFactoryTests
Tomcat tomcat = ((TomcatEmbeddedServletContainer) this.container).getTomcat(); Tomcat tomcat = ((TomcatEmbeddedServletContainer) this.container).getTomcat();
Connector connector = tomcat.getConnector(); Connector connector = tomcat.getConnector();
AbstractHttp11JsseProtocol<?> jsseProtocol = (AbstractHttp11JsseProtocol<?>) connector this.container.start();
.getProtocolHandler(); SSLHostConfig sslHostConfig = connector.getProtocolHandler()
assertThat(jsseProtocol.getSslProtocol()).isEqualTo("TLS"); .findSslHostConfigs()[0];
assertThat(jsseProtocol.getProperty("sslEnabledProtocols")).isEqualTo("TLSv1.2"); assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS");
assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2");
} }
@Test @Test