Commit 0960908b authored by Andy Wilkinson's avatar Andy Wilkinson

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
parent d26ecbef
......@@ -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;
}
}
......@@ -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
......
......@@ -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
......
......@@ -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>
......
<?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>
/*
* 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);
}
}
/*
* 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";
}
}
server.port = 8443
server.ssl.key-store = sample.jks
server.ssl.key-store-password = secret
server.ssl.key-password = password
\ No newline at end of file
/*
* 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());
}
}
......@@ -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
*/
......
......@@ -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);
}
/*
* 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;
}
}
......@@ -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
......
......@@ -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
......
......@@ -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;
......
......@@ -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;
......
......@@ -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];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment