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>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -173,7 +173,7 @@
<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-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>
<velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.version>
@ -1314,11 +1314,6 @@
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
@ -1334,6 +1329,11 @@
<artifactId>tomcat-jsp-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>

View File

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

View File

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

View File

@ -193,7 +193,7 @@ The following sample applications are provided:
| Embedded Tomcat
| 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]
| 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]
| 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]
| 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-multi-connectors</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-undertow</module>
<module>spring-boot-sample-undertow-ssl</module>

View File

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

View File

@ -8,7 +8,7 @@
<version>1.4.0.BUILD-SNAPSHOT</version>
</parent>
<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>
<url>http://projects.spring.io/spring-boot/</url>
<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>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>

View File

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

View File

@ -110,8 +110,8 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
<optional>true</optional>
</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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
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.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -48,11 +55,13 @@ import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
@ -301,7 +310,7 @@ public class TomcatEmbeddedServletContainerFactory
Compression compression = getCompression();
protocol.setCompression("on");
protocol.setCompressionMinSize(compression.getMinResponseSize());
protocol.setCompressableMimeTypes(
protocol.setCompressableMimeType(
StringUtils.arrayToCommaDelimitedString(compression.getMimeTypes()));
if (getCompression().getExcludedUserAgents() != null) {
protocol.setNoCompressionUserAgents(
@ -323,13 +332,33 @@ public class TomcatEmbeddedServletContainerFactory
protocol.setKeystorePass(ssl.getKeyStorePassword());
protocol.setKeyPass(ssl.getKeyPassword());
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) {
protocol.setProperty("sslEnabledProtocols",
StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
try {
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) {
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 {
configureSslKeyStore(protocol, ssl);
@ -350,10 +379,6 @@ public class TomcatEmbeddedServletContainerFactory
SslStoreProvider sslStoreProvider) {
Assert.isInstanceOf(Http11NioProtocol.class, protocol,
"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) {
@ -677,6 +702,89 @@ public class TomcatEmbeddedServletContainerFactory
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 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 {
@SuppressWarnings("deprecation")
private final String MERGED_WEB_XML = org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML;
private static final String MERGED_WEB_XML = "org.apache.tomcat.util.scan.MergedWebXml";
@Override
public void lifecycleEvent(LifecycleEvent event) {
@ -764,8 +871,10 @@ public class TomcatEmbeddedServletContainerFactory
private void onStart(Context context) {
ServletContext servletContext = context.getServletContext();
if (servletContext.getAttribute(this.MERGED_WEB_XML) == null) {
servletContext.setAttribute(this.MERGED_WEB_XML, getEmptyWebXml());
if (servletContext
.getAttribute(StoreMergedWebXmlListener.MERGED_WEB_XML) == null) {
servletContext.setAttribute(StoreMergedWebXmlListener.MERGED_WEB_XML,
getEmptyWebXml());
}
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.DefaultBeanNameGenerator;
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.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
@ -971,8 +971,8 @@ public class SpringApplicationTests {
static class ExampleWebConfig {
@Bean
public JettyEmbeddedServletContainerFactory container() {
return new JettyEmbeddedServletContainerFactory(0);
public TomcatEmbeddedServletContainerFactory container() {
return new TomcatEmbeddedServletContainerFactory(0);
}
}

View File

@ -55,6 +55,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.InputStreamFactory;
import org.apache.http.client.protocol.HttpClientContext;
@ -124,6 +125,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
@BeforeClass
@AfterClass
public static void uninstallUrlStreamHandlerFactory() {
ReflectionTestUtils.setField(TomcatURLStreamHandlerFactory.class, "instance",
null);
ReflectionTestUtils.setField(URL.class, "factory", null);
}
@ -589,7 +592,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
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);
}

View File

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