Add support for configuring SSL declaratively

Both Tomcat and Jetty can now be configured to use SSL via the
environment (typically application.properties or application.yml)

Closes #1084
This commit is contained in:
Andy Wilkinson 2014-07-22 17:51:51 +01:00
parent d26ecbef04
commit 0960908bd7
19 changed files with 777 additions and 80 deletions

View File

@ -33,6 +33,7 @@ import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletCont
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
@ -46,6 +47,7 @@ import org.springframework.util.StringUtils;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = false)
public class ServerProperties implements EmbeddedServletContainerCustomizer {
@ -58,6 +60,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer {
private String contextPath;
private Ssl ssl;
@NotNull
private String servletPath = "/";
@ -131,6 +135,14 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer {
this.sessionTimeout = sessionTimeout;
}
public Ssl getSsl() {
return this.ssl;
}
public void setSsl(Ssl ssl) {
this.ssl = ssl;
}
public void setLoader(String value) {
// no op to support Tomcat running as a traditional container (not embedded)
}
@ -149,12 +161,41 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer {
if (getSessionTimeout() != null) {
container.setSessionTimeout(getSessionTimeout());
}
if (getSsl() != null) {
container.setSsl(getSsl());
}
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat()
.customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
}
}
public String[] getPathsArray(Collection<String> paths) {
String[] result = new String[paths.size()];
int i = 0;
for (String path : paths) {
result[i++] = getPath(path);
}
return result;
}
public String[] getPathsArray(String[] paths) {
String[] result = new String[paths.length];
int i = 0;
for (String path : paths) {
result[i++] = getPath(path);
}
return result;
}
public String getPath(String path) {
String prefix = getServletPrefix();
if (!path.startsWith("/")) {
path = "/" + path;
}
return prefix + path;
}
public static class Tomcat {
private String accessLogPattern;
@ -313,31 +354,4 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer {
}
}
public String[] getPathsArray(Collection<String> paths) {
String[] result = new String[paths.size()];
int i = 0;
for (String path : paths) {
result[i++] = getPath(path);
}
return result;
}
public String[] getPathsArray(String[] paths) {
String[] result = new String[paths.length];
int i = 0;
for (String path : paths) {
result[i++] = getPath(path);
}
return result;
}
public String getPath(String path) {
String prefix = getServletPrefix();
if (!path.startsWith("/")) {
path = "/" + path;
}
return prefix + path;
}
}

View File

@ -56,6 +56,14 @@ content into your application; rather pick only the properties that you need.
server.session-timeout= # session timeout in seconds
server.context-path= # the context path, defaults to '/'
server.servlet-path= # the servlet path, defaults to '/'
server.ssl.client-auth= # want or need
server.ssl.key-alias=
server.ssl.key-password=
server.ssl.key-store=
server.ssl.key-store-password=
server.ssl.protocol=TLS
server.ssl.trust-store=
server.ssl.trust-store-password=
server.tomcat.access-log-pattern= # log pattern of the access log
server.tomcat.access-log-enabled=false # is access logging enabled
server.tomcat.protocol-header=x-forwarded-proto # ssl forward headers

View File

@ -387,6 +387,25 @@ and then inject the actual (``local'') port as a `@Value`. For example:
}
----
[[howto-configure-ssl]]
=== Configure SSL
SSL can be configured declaratively by setting the various `server.ssl.*` properties,
typically in `application.properties` or `application.yml`. For example:
[source,properties,indent=0,subs="verbatim,quotes,attributes"]
----
server.port = 8443
server.ssl.key-store = classpath:keystore.jks
server.ssl.key-store-password = secret
server.ssl.key-password = another-secret
----
See {sc-spring-boot}/context/embedded/Ssl.{sc-ext}[`Ssl`] for details of all of the
supported properties.
NOTE: Tomcat requires the key store (and trust store if you're using one) to be directly
accessible on the filesystem, i.e. it cannot be read from within a jar file.
[[howto-configure-tomcat]]
@ -401,56 +420,6 @@ nuclear option is to add your own `TomcatEmbeddedServletContainerFactory`.
[[howto-terminate-ssl-in-tomcat]]
=== Terminate SSL in Tomcat
Use an `EmbeddedServletContainerCustomizer` and in that add a `TomcatConnectorCustomizer`
that sets up the connector to be secure:
[source,java,indent=0,subs="verbatim,quotes,attributes"]
----
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
// ...
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
@Override
public void customize(ConfigurableEmbeddedServletContainer factory) {
if(factory instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) factory));
}
}
public void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setPort(serverPort);
connector.setSecure(true);
connector.setScheme("https");
connector.setAttribute("keyAlias", "tomcat");
connector.setAttribute("keystorePass", "password");
try {
connector.setAttribute("keystoreFile",
ResourceUtils.getFile("src/ssl/tomcat.keystore").getAbsolutePath());
} catch (FileNotFoundException e) {
throw new IllegalStateException("Cannot load keystore", e);
}
connector.setAttribute("clientAuth", "false");
connector.setAttribute("sslProtocol", "TLS");
connector.setAttribute("SSLEnabled", true);
}
});
}
}
----
[[howto-enable-multiple-connectors-in-tomcat]]
=== Enable Multiple Connectors Tomcat
Add a `org.apache.catalina.connector.Connector` to the

View File

@ -44,6 +44,7 @@
<module>spring-boot-sample-secure</module>
<module>spring-boot-sample-servlet</module>
<module>spring-boot-sample-simple</module>
<module>spring-boot-sample-tomcat-ssl</module>
<module>spring-boot-sample-tomcat</module>
<module>spring-boot-sample-tomcat-multi-connectors</module>
<module>spring-boot-sample-tomcat8-jsp</module>

View File

@ -0,0 +1,56 @@
<?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.2.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-tomcat-ssl</artifactId>
<name>Spring Boot Tomcat Sample</name>
<description>Spring Boot Tomcat 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>
</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,34 @@
/*
* Copyright 2012-2014 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;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
public class SampleTomcatSslApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleTomcatSslApplication.class, args);
}
}

View File

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

View File

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

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2014 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;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleTomcatSslApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:0")
@DirtiesContext
public class SampleTomcatSslApplicationTests {
@Value("${local.server.port}")
private int port;
@Test
public void testHome() throws Exception {
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder().loadTrustMaterial(null,
new TrustSelfSignedStrategy()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
TestRestTemplate testRestTemplate = new TestRestTemplate();
((HttpComponentsClientHttpRequestFactory) testRestTemplate.getRequestFactory())
.setHttpClient(httpClient);
ResponseEntity<String> entity = testRestTemplate.getForEntity(
"https://localhost:" + this.port, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("Hello, world", entity.getBody());
}
}

View File

@ -59,6 +59,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements
private int sessionTimeout;
private Ssl ssl;
/**
* Create a new {@link AbstractConfigurableEmbeddedServletContainer} instance.
*/
@ -247,6 +249,15 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements
this.jspServletClassName = jspServletClassName;
}
@Override
public void setSsl(Ssl ssl) {
this.ssl = ssl;
}
public Ssl getSsl() {
return this.ssl;
}
/**
* @return the JSP servlet class name
*/

View File

@ -139,4 +139,10 @@ public interface ConfigurableEmbeddedServletContainer {
*/
void addInitializers(ServletContextInitializer... initializers);
/**
* Sets the SSL configuration that will be applied to the container's default
* connector.
* @param ssl the SSL configuration
*/
void setSsl(Ssl ssl);
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2012-2014 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;
/**
* Simple container-independent abstraction for SSL configuration.
*
* @author Andy Wilkinson
* @since 1.2.0
*/
public class Ssl {
private ClientAuth clientAuth;
private String[] ciphers;
private String keyAlias;
private String keyPassword;
private String keyStore;
private String keyStorePassword;
private String trustStore;
private String trustStorePassword;
private String protocol = "TLS";
public ClientAuth getClientAuth() {
return this.clientAuth;
}
public void setClientAuth(ClientAuth clientAuth) {
this.clientAuth = clientAuth;
}
public String[] getCiphers() {
return this.ciphers;
}
public void setCiphers(String[] ciphers) {
this.ciphers = ciphers;
}
public String getKeyAlias() {
return this.keyAlias;
}
public void setKeyAlias(String keyAlias) {
this.keyAlias = keyAlias;
}
public String getKeyPassword() {
return this.keyPassword;
}
public void setKeyPassword(String keyPassword) {
this.keyPassword = keyPassword;
}
public String getKeyStore() {
return this.keyStore;
}
public void setKeyStore(String keyStore) {
this.keyStore = keyStore;
}
public String getKeyStorePassword() {
return this.keyStorePassword;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public String getTrustStore() {
return this.trustStore;
}
public void setTrustStore(String trustStore) {
this.trustStore = trustStore;
}
public String getTrustStorePassword() {
return this.trustStorePassword;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public String getProtocol() {
return this.protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public enum ClientAuth {
WANT, NEED;
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.context.embedded.jetty;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
@ -24,25 +25,32 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.ssl.SslSocketConnector;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ErrorPage;
import org.springframework.boot.context.embedded.MimeMappings;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
@ -104,6 +112,16 @@ public class JettyEmbeddedServletContainerFactory extends
configureWebAppContext(context, initializers);
server.setHandler(context);
this.logger.info("Server initialized with port: " + port);
if (getSsl() != null) {
SslContextFactory sslContextFactory = new SslContextFactory();
configureSslContextFactory(sslContextFactory, getSsl());
SslSocketConnector sslConnector = new SslSocketConnector(sslContextFactory);
sslConnector.setPort(port);
server.setConnectors(new Connector[] { sslConnector });
}
for (JettyServerCustomizer customizer : getServerCustomizers()) {
customizer.customize(server);
}
@ -111,6 +129,52 @@ public class JettyEmbeddedServletContainerFactory extends
return getJettyEmbeddedServletContainer(server);
}
protected void configureSslContextFactory(SslContextFactory sslContextFactory, Ssl ssl) {
sslContextFactory.setProtocol(getSsl().getProtocol());
if (getSsl().getClientAuth() == ClientAuth.NEED) {
sslContextFactory.setNeedClientAuth(true);
sslContextFactory.setWantClientAuth(true);
}
else if (getSsl().getClientAuth() == ClientAuth.WANT) {
sslContextFactory.setWantClientAuth(true);
}
if (getSsl().getKeyStorePassword() != null) {
sslContextFactory.setKeyStorePassword(getSsl().getKeyStorePassword());
}
if (getSsl().getKeyPassword() != null) {
sslContextFactory.setKeyManagerPassword(getSsl().getKeyPassword());
}
sslContextFactory.setCertAlias(getSsl().getKeyAlias());
try {
sslContextFactory.setKeyStoreResource(Resource.newResource(ResourceUtils
.getURL(getSsl().getKeyStore())));
}
catch (IOException e) {
throw new EmbeddedServletContainerException("Could not find key store '"
+ getSsl().getKeyStore() + "'", e);
}
if (getSsl().getCiphers() != null) {
sslContextFactory.setIncludeCipherSuites(getSsl().getCiphers());
}
if (getSsl().getTrustStorePassword() != null) {
sslContextFactory.setTrustStorePassword(getSsl().getTrustStorePassword());
}
if (getSsl().getTrustStore() != null) {
try {
sslContextFactory.setTrustStoreResource(Resource
.newResource(ResourceUtils.getURL(getSsl().getTrustStore())));
}
catch (IOException e) {
throw new EmbeddedServletContainerException(
"Could not find trust store '" + getSsl().getTrustStore() + "'",
e);
}
}
}
/**
* Configure the given Jetty {@link WebAppContext} for use.
* @param context the context to configure

View File

@ -17,6 +17,7 @@
package org.springframework.boot.context.embedded.tomcat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
@ -41,6 +42,7 @@ import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
@ -49,12 +51,16 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory
import org.springframework.boot.context.embedded.ErrorPage;
import org.springframework.boot.context.embedded.MimeMappings;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* {@link EmbeddedServletContainerFactory} that can be used to create
@ -230,11 +236,64 @@ public class TomcatEmbeddedServletContainerFactory extends
// If ApplicationContext is slow to start we want Tomcat not to bind to the socket
// prematurely...
connector.setProperty("bindOnInit", "false");
if (getSsl() != null) {
if (connector.getProtocolHandler() instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol jsseProtocol = (AbstractHttp11JsseProtocol) connector
.getProtocolHandler();
configureJsseProtocol(jsseProtocol, getSsl());
connector.setScheme("https");
connector.setSecure(true);
}
else {
throw new IllegalStateException(
"To use SSL, the connector's protocol handler must be an AbstractHttp11JsseProtocol subclass");
}
}
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}
}
protected void configureJsseProtocol(AbstractHttp11JsseProtocol jsseProtocol, Ssl ssl) {
jsseProtocol.setSSLEnabled(true);
jsseProtocol.setSslProtocol(getSsl().getProtocol());
if (getSsl().getClientAuth() == ClientAuth.NEED) {
jsseProtocol.setClientAuth(Boolean.TRUE.toString());
}
else if (getSsl().getClientAuth() == ClientAuth.WANT) {
jsseProtocol.setClientAuth("want");
}
jsseProtocol.setKeystorePass(getSsl().getKeyStorePassword());
jsseProtocol.setKeyPass(getSsl().getKeyPassword());
jsseProtocol.setKeyAlias(getSsl().getKeyAlias());
try {
jsseProtocol.setKeystoreFile(ResourceUtils.getFile(getSsl().getKeyStore())
.getAbsolutePath());
}
catch (FileNotFoundException e) {
throw new EmbeddedServletContainerException("Could not find key store "
+ getSsl().getKeyStore(), e);
}
jsseProtocol.setCiphers(StringUtils.arrayToCommaDelimitedString(getSsl()
.getCiphers()));
if (getSsl().getTrustStore() != null) {
try {
jsseProtocol.setTruststoreFile(ResourceUtils.getFile(
getSsl().getTrustStore()).getAbsolutePath());
}
catch (FileNotFoundException e) {
throw new EmbeddedServletContainerException("Could not find trust store "
+ getSsl().getTrustStore(), e);
}
}
jsseProtocol.setTruststorePass(getSsl().getTrustStorePassword());
}
/**
* Configure the Tomcat {@link Context}.
* @param context the Tomcat context

View File

@ -16,11 +16,14 @@
package org.springframework.boot.context.embedded;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@ -31,12 +34,18 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.mockito.InOrder;
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequest;
@ -62,6 +71,7 @@ import static org.mockito.Mockito.mock;
*
* @author Phillip Webb
* @author Greg Turnquist
* @author Andy Wilkinson
*/
public abstract class AbstractEmbeddedServletContainerFactoryTests {
@ -300,8 +310,192 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
assertThat(getResponse(getLocalUrl("/bang")), equalTo("Hello World"));
}
@Test
public void basicSsl() throws Exception {
FileCopyUtils.copy("test",
new FileWriter(this.temporaryFolder.newFile("test.txt")));
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setDocumentRoot(this.temporaryFolder.getRoot());
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer();
this.container.start();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder().loadTrustMaterial(null,
new TrustSelfSignedStrategy()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory),
equalTo("test"));
}
@Test
public void sslNeedsClientAuthenticationSucceedsWithClientCertificate()
throws Exception {
FileCopyUtils.copy("test",
new FileWriter(this.temporaryFolder.newFile("test.txt")));
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setDocumentRoot(this.temporaryFolder.getRoot());
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setClientAuth(ClientAuth.NEED);
ssl.setTrustStore("src/test/resources/test.jks");
ssl.setTrustStorePassword("secret");
factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer();
this.container.start();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File("src/test/resources/test.jks")),
"secret".toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, "password".toCharArray()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory),
equalTo("test"));
}
@Test(expected = IOException.class)
public void sslNeedsClientAuthenticationFailsWithoutClientCertificate()
throws Exception {
FileCopyUtils.copy("test",
new FileWriter(this.temporaryFolder.newFile("test.txt")));
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setDocumentRoot(this.temporaryFolder.getRoot());
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setClientAuth(ClientAuth.NEED);
ssl.setTrustStore("src/test/resources/test.jks");
ssl.setTrustStorePassword("secret");
factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer();
this.container.start();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder().loadTrustMaterial(null,
new TrustSelfSignedStrategy()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
getResponse(getLocalUrl("https", "/test.txt"), requestFactory);
}
@Test
public void sslWantsClientAuthenticationSucceedsWithClientCertificate()
throws Exception {
FileCopyUtils.copy("test",
new FileWriter(this.temporaryFolder.newFile("test.txt")));
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setDocumentRoot(this.temporaryFolder.getRoot());
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setClientAuth(ClientAuth.WANT);
ssl.setTrustStore("src/test/resources/test.jks");
ssl.setTrustStorePassword("secret");
factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer();
this.container.start();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File("src/test/resources/test.jks")),
"secret".toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, "password".toCharArray()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory),
equalTo("test"));
}
@Test
public void sslWantsClientAuthenticationSucceedsWithoutClientCertificate()
throws Exception {
FileCopyUtils.copy("test",
new FileWriter(this.temporaryFolder.newFile("test.txt")));
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setDocumentRoot(this.temporaryFolder.getRoot());
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setClientAuth(ClientAuth.WANT);
ssl.setTrustStore("src/test/resources/test.jks");
ssl.setTrustStorePassword("secret");
factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer();
this.container.start();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder().loadTrustMaterial(null,
new TrustSelfSignedStrategy()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory),
equalTo("test"));
}
protected String getLocalUrl(String resourcePath) {
return "http://localhost:" + this.container.getPort() + resourcePath;
return getLocalUrl("http", resourcePath);
}
protected String getLocalUrl(String scheme, String resourcePath) {
return scheme + "://localhost:" + this.container.getPort() + resourcePath;
}
protected String getLocalUrl(int port, String resourcePath) {
@ -318,10 +512,27 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
}
}
protected String getResponse(String url,
HttpComponentsClientHttpRequestFactory requestFactory) throws IOException,
URISyntaxException {
ClientHttpResponse response = getClientResponse(url, requestFactory);
try {
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
}
finally {
response.close();
}
}
protected ClientHttpResponse getClientResponse(String url) throws IOException,
URISyntaxException {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
ClientHttpRequest request = clientHttpRequestFactory.createRequest(new URI(url),
return getClientResponse(url, new HttpComponentsClientHttpRequestFactory());
}
protected ClientHttpResponse getClientResponse(String url,
HttpComponentsClientHttpRequestFactory requestFactory) throws IOException,
URISyntaxException {
ClientHttpRequest request = requestFactory.createRequest(new URI(url),
HttpMethod.GET);
ClientHttpResponse response = request.execute();
return response;

View File

@ -21,11 +21,13 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ssl.SslConnector;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
import org.springframework.boot.context.embedded.Ssl;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@ -39,6 +41,7 @@ import static org.mockito.Mockito.mock;
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
*/
public class JettyEmbeddedServletContainerFactoryTests extends
AbstractEmbeddedServletContainerFactoryTests {
@ -94,6 +97,25 @@ public class JettyEmbeddedServletContainerFactoryTests extends
assertTimeout(factory, 60);
}
@Test
public void sslCiphersConfiguration() throws Exception {
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStorePassword("secret");
ssl.setKeyPassword("password");
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
JettyEmbeddedServletContainerFactory factory = getFactory();
factory.setSsl(ssl);
this.container = factory.getEmbeddedServletContainer();
JettyEmbeddedServletContainer jettyContainer = (JettyEmbeddedServletContainer) this.container;
SslConnector sslConnector = (SslConnector) jettyContainer.getServer()
.getConnectors()[0];
assertThat(sslConnector.getSslContextFactory().getIncludeCipherSuites(),
equalTo(new String[] { "ALPHA", "BRAVO", "CHARLIE" }));
}
private void assertTimeout(JettyEmbeddedServletContainerFactory factory, int expected) {
this.container = factory.getEmbeddedServletContainer();
JettyEmbeddedServletContainer jettyContainer = (JettyEmbeddedServletContainer) this.container;

View File

@ -28,9 +28,11 @@ import org.apache.catalina.Service;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.util.SocketUtils;
import static org.hamcrest.Matchers.equalTo;
@ -221,6 +223,24 @@ public class TomcatEmbeddedServletContainerFactoryTests extends
assertEquals("UTF-8", tomcat.getConnector().getURIEncoding());
}
@Test
public void sslCiphersConfiguration() throws Exception {
Ssl ssl = new Ssl();
ssl.setKeyStore("test.jks");
ssl.setKeyStorePassword("secret");
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
TomcatEmbeddedServletContainerFactory factory = getFactory();
factory.setSsl(ssl);
Tomcat tomcat = getTomcat(factory);
Connector connector = tomcat.getConnector();
AbstractHttp11JsseProtocol jsseProtocol = (AbstractHttp11JsseProtocol) connector
.getProtocolHandler();
assertThat(jsseProtocol.getCiphers(), equalTo("ALPHA,BRAVO,CHARLIE"));
}
private void assertTimeout(TomcatEmbeddedServletContainerFactory factory, int expected) {
Tomcat tomcat = getTomcat(factory);
Context context = (Context) tomcat.getHost().findChildren()[0];

Binary file not shown.