From 8038882d46e3a3f9eb78c3f0993c68edf25cd280 Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Wed, 16 Nov 2016 22:26:39 +0900 Subject: [PATCH 1/8] Polish Closes gh-7403 --- .../boot/actuate/endpoint/mvc/NamedMvcEndpoint.java | 2 +- .../boot/autoconfigure/jooq/JooqExceptionTranslator.java | 2 +- .../condition/ConditionalOnCloudPlatformTests.java | 8 ++++++++ .../autoconfigure/web/WebMvcAutoConfigurationTests.java | 2 +- .../src/main/asciidoc/production-ready-features.adoc | 4 ++-- .../src/main/asciidoc/spring-boot-features.adoc | 2 +- spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc | 2 +- .../spring-boot-sample-test-nomockito/pom.xml | 1 + .../testnomockito/SampleTestNoMockitoApplicationTest.java | 2 +- .../org/springframework/boot/loader/tools/RunProcess.java | 4 ++-- .../boot/loader/archive/ExplodedArchive.java | 2 +- .../org/springframework/boot/maven/AbstractRunMojo.java | 2 +- 12 files changed, 21 insertions(+), 12 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java index 87e9a59188..f92fb0eecf 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java @@ -17,7 +17,7 @@ package org.springframework.boot.actuate.endpoint.mvc; /** - * A {@link MvcEndpoint} that also includes a logical name. Unlike {@link #getPath() + * An {@link MvcEndpoint} that also includes a logical name. Unlike {@link #getPath() * endpoints paths}, it should not be possible for a user to change the endpoint name. * Names provide a consistent way to reference an endpoint, for example they may be used * as the {@literal 'rel'} attribute in a HAL response. diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java index d1f9de3913..778f83df54 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqExceptionTranslator.java @@ -30,7 +30,7 @@ import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; /** - * Transforms {@link java.sql.SQLException} into a Spring-specific @{link + * Transforms {@link java.sql.SQLException} into a Spring-specific {@link * DataAccessException}. * * @author Lukas Eder diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java index f2de5088d2..9d9aa5a5a0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.condition; +import org.junit.After; import org.junit.Test; import org.springframework.boot.cloud.CloudPlatform; @@ -33,6 +34,13 @@ public class ConditionalOnCloudPlatformTests { private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + @After + public void cleanUp() { + if (this.context != null) { + this.context.close(); + } + } + @Test public void outcomeWhenCloudfoundryPlatformNotPresentShouldNotMatch() throws Exception { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index 9b17db8c2f..478772ba3a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -577,7 +577,7 @@ public class WebMvcAutoConfigurationTests { } @Test - public void welcomePageMappingDoesNotHandleRequestThatDoNotAcceptTextHtml() + public void welcomePageMappingDoesNotHandleRequestsThatDoNotAcceptTextHtml() throws Exception { load("spring.resources.static-locations:classpath:/welcome-page/"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 1a78e186db..52d5256dca 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -953,7 +953,7 @@ documentation]. == Loggers Spring Boot Actuator includes the ability to view and configure the log levels of your application at runtime. You can view either the entire list or an individual logger's -configuration which is made up of both the explictily configured logging level as well as +configuration which is made up of both the explicitly configured logging level as well as the effective logging level given to it by the logging framework. These levels can be: * `TRACE` @@ -965,7 +965,7 @@ the effective logging level given to it by the logging framework. These levels * `OFF` * `null` -with `null` indicating that there is no explict configuration. +with `null` indicating that there is no explicit configuration. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index fd53aae9e8..5219cc0510 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3886,7 +3886,7 @@ abstraction expects. No further customization is applied on it. EhCache 2.x is used if a file named `ehcache.xml` can be found at the root of the classpath. If EhCache 2.x, the `EhCacheCacheManager` provided by the `spring-boot-starter-cache` '`Starter`' and such file is present it is used to bootstrap -the cache manager. An alternate configuration file can be provide a well using: +the cache manager. An alternate configuration file can be provided as well using: [source,properties,indent=0] ---- diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 608f83a09b..00b0b781ef 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -769,7 +769,7 @@ dependencies out-of-the-box so you may want to have a look to the {propdeps-plugin}[`propdeps-plugin`] in the meantime. TIP: repackaged archives do not contain devtools by default. If you want to use -<>, you'll need to enable the +<>, you'll need to disable the `excludeDevtools` build property to include it. The property is supported with both the Maven and Gradle plugins. diff --git a/spring-boot-samples/spring-boot-sample-test-nomockito/pom.xml b/spring-boot-samples/spring-boot-sample-test-nomockito/pom.xml index b069b69fa5..d97548d047 100644 --- a/spring-boot-samples/spring-boot-sample-test-nomockito/pom.xml +++ b/spring-boot-samples/spring-boot-sample-test-nomockito/pom.xml @@ -42,6 +42,7 @@ org.assertj assertj-core + test diff --git a/spring-boot-samples/spring-boot-sample-test-nomockito/src/test/java/sample/testnomockito/SampleTestNoMockitoApplicationTest.java b/spring-boot-samples/spring-boot-sample-test-nomockito/src/test/java/sample/testnomockito/SampleTestNoMockitoApplicationTest.java index 589bb0c4f1..70e89a1f24 100644 --- a/spring-boot-samples/spring-boot-sample-test-nomockito/src/test/java/sample/testnomockito/SampleTestNoMockitoApplicationTest.java +++ b/spring-boot-samples/spring-boot-sample-test-nomockito/src/test/java/sample/testnomockito/SampleTestNoMockitoApplicationTest.java @@ -10,7 +10,7 @@ import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests that {code ResetMocksTestExecutionListener} and + * Tests that {@code ResetMocksTestExecutionListener} and * {@code MockitoTestExecutionListener} gracefully degrade when Mockito is not on the * classpath. * diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java index 6cc20a4e44..ef6f62f8b3 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java @@ -52,7 +52,7 @@ public class RunProcess { /** * Creates new {@link RunProcess} instance for the specified command. - * @param command the program to execute and it's arguments + * @param command the program to execute and its arguments */ public RunProcess(String... command) { this(null, command); @@ -63,7 +63,7 @@ public class RunProcess { * command. * @param workingDirectory the working directory of the child process or {@code null} * to run in the working directory of the current Java process - * @param command the program to execute and it's arguments + * @param command the program to execute and its arguments */ public RunProcess(File workingDirectory, String... command) { this.workingDirectory = workingDirectory; diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java index 5f6b8fb63e..6efca10a31 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java @@ -65,7 +65,7 @@ public class ExplodedArchive implements Archive { * Create a new {@link ExplodedArchive} instance. * @param root the root folder * @param recursive if recursive searching should be used to locate the manifest. - * Defaults to {@code true}, folders with a large tree might want to set this to {code + * Defaults to {@code true}, folders with a large tree might want to set this to {@code * false}. */ public ExplodedArchive(File root, boolean recursive) { diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java index b22b1b7e57..dc08eff497 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java @@ -89,7 +89,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Current working directory to use for the application. If not specified, basedir - * will be used NOTE: the use of working directory means that processes will be + * will be used. NOTE: the use of working directory means that processes will be * started by forking a new JVM. * @since 1.5 */ From 7daf69a393751c3c5d3c4fac5a9d188c4bd54baa Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 16 Nov 2016 13:44:18 -0800 Subject: [PATCH 2/8] Upgrade to HTMLUnit 2.23 (+selenium module 2.23.2) Fixes gh-7232 --- spring-boot-dependencies/pom.xml | 5 +++-- .../webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 4ac2a43fc9..b5748f3acc 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -89,7 +89,7 @@ 2.4.7 2.3.13 2.3.3 - 2.21 + 2.23 4.1.2 4.5.2 4.4.5 @@ -139,6 +139,7 @@ 2.0.8.RELEASE 2.0.7.RELEASE 2.53.1 + 2.23.2 2.2.2 3.1.0 1.1.1 @@ -1923,7 +1924,7 @@ org.seleniumhq.selenium htmlunit-driver - ${htmlunit.version} + ${selenium-htmlunit.version} org.seleniumhq.selenium diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java index 66ed6e53bb..b618de2ce9 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriverTests.java @@ -81,7 +81,9 @@ public class LocalHostWebConnectionHtmlUnitDriverTests { throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Environment must not be null"); - new LocalHostWebConnectionHtmlUnitDriver(null, mock(Capabilities.class)); + Capabilities capabilities = mock(Capabilities.class); + given(capabilities.getBrowserName()).willReturn("chrome"); + new LocalHostWebConnectionHtmlUnitDriver(null, capabilities); } @Test From 004528777f411e73b4b828b0edbe8aa9d14a3e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Daviet?= Date: Tue, 15 Nov 2016 11:19:56 +0100 Subject: [PATCH 3/8] Make ErrorPageFilter.getDescription protected Make `ErrorPageFilter#getDescription` `protected` instead of `private` to be able to customize the details for the request logged in case of an error. Fixes gh-7380 Closes gh-7393 --- .../org/springframework/boot/web/support/ErrorPageFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java b/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java index 484bc42d08..6610a22c33 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java @@ -185,7 +185,7 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { request.getRequestDispatcher(path).forward(request, response); } - private String getDescription(HttpServletRequest request) { + protected String getDescription(HttpServletRequest request) { return "[" + request.getServletPath() + (request.getPathInfo() == null ? "" : request.getPathInfo()) + "]"; } From 12d9ebfc02338162ba99ccaa1640a8ce3c8a5efa Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 16 Nov 2016 13:59:45 -0800 Subject: [PATCH 4/8] Polish ErrorPageFilter.getDescription javadoc See gh-7393 --- .../springframework/boot/web/support/ErrorPageFilter.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java b/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java index 6610a22c33..19a612ec75 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java @@ -185,6 +185,13 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { request.getRequestDispatcher(path).forward(request, response); } + /** + * Return the description for the given request. By default this method will return a + * description based on the request {@code servletPath} and {@code pathInfo}. + * @param request the source request + * @return the description + * @since 1.5.0 + */ protected String getDescription(HttpServletRequest request) { return "[" + request.getServletPath() + (request.getPathInfo() == null ? "" : request.getPathInfo()) + "]"; From d7bbea63b77d55db0db34f9f488ac6e51debdcfa Mon Sep 17 00:00:00 2001 From: Christoffer Sawicki Date: Sat, 12 Nov 2016 20:36:54 +0100 Subject: [PATCH 5/8] Add properties for content security policy Add `content-security-policy` and `content-security-policy-mode` `security.header` properties and update auto-configuration to apply them when specified. Fixes gh-7373 Closes gh-7357 --- .../security/SecurityProperties.java | 38 +++++++++++++++++++ .../SpringBootWebSecurityConfiguration.java | 9 +++++ ...ringBootWebSecurityConfigurationTests.java | 34 ++++++++++++++++- .../appendix-application-properties.adoc | 2 + 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java index 6126f61966..97d2ad4795 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java @@ -174,6 +174,18 @@ public class SecurityProperties implements SecurityPrerequisite { NONE, DOMAIN, ALL } + public enum ContentSecurityPolicyMode { + /** + * Use the {@code Content-Security-Policy} header. + */ + DEFAULT, + + /** + * Use the {@code Content-Security-Policy-Report-Only} header. + */ + REPORT_ONLY + } + /** * Enable cross site scripting (XSS) protection. */ @@ -194,6 +206,16 @@ public class SecurityProperties implements SecurityPrerequisite { */ private boolean contentType = true; + /** + * Value for content security policy header. + */ + private String contentSecurityPolicy; + + /** + * Whether to use the "Content-Security-Policy" or "Content-Security-Policy-Report-Only" header. + */ + private ContentSecurityPolicyMode contentSecurityPolicyMode = ContentSecurityPolicyMode.DEFAULT; + /** * HTTP Strict Transport Security (HSTS) mode (none, domain, all). */ @@ -231,6 +253,22 @@ public class SecurityProperties implements SecurityPrerequisite { this.contentType = contentType; } + public String getContentSecurityPolicy() { + return this.contentSecurityPolicy; + } + + public void setContentSecurityPolicy(String contentSecurityPolicy) { + this.contentSecurityPolicy = contentSecurityPolicy; + } + + public ContentSecurityPolicyMode getContentSecurityPolicyMode() { + return this.contentSecurityPolicyMode; + } + + public void setContentSecurityPolicyMode(ContentSecurityPolicyMode contentSecurityPolicyMode) { + this.contentSecurityPolicyMode = contentSecurityPolicyMode; + } + public HSTS getHsts() { return this.hsts; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java index 22747e1ad7..daa5e3569a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java @@ -108,6 +108,15 @@ public class SpringBootWebSecurityConfiguration { if (!headers.isContentType()) { configurer.contentTypeOptions().disable(); } + if (StringUtils.hasText(headers.getContentSecurityPolicy())) { + if (headers.getContentSecurityPolicyMode() == Headers.ContentSecurityPolicyMode.DEFAULT) { + configurer.contentSecurityPolicy(headers.getContentSecurityPolicy()); + } + else { + assert headers.getContentSecurityPolicyMode() == Headers.ContentSecurityPolicyMode.REPORT_ONLY; + configurer.contentSecurityPolicy(headers.getContentSecurityPolicy()).reportOnly(); + } + } if (!headers.isXss()) { configurer.xssProtection().disable(); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java index 82d6956546..23af97a970 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java @@ -213,7 +213,8 @@ public class SpringBootWebSecurityConfigurationTests { .andExpect(MockMvcResultMatchers.header().string("Cache-Control", is(notNullValue()))) .andExpect(MockMvcResultMatchers.header().string("X-Frame-Options", - is(notNullValue()))); + is(notNullValue()))) + .andExpect(MockMvcResultMatchers.header().doesNotExist("Content-Security-Policy")); } @Test @@ -239,6 +240,37 @@ public class SpringBootWebSecurityConfigurationTests { MockMvcResultMatchers.header().doesNotExist("X-Frame-Options")); } + @Test + public void contentSecurityPolicyConfiguration() throws Exception { + this.context = SpringApplication.run(VanillaWebConfiguration.class, + "--security.headers.content-security-policy=default-src 'self';"); + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup((WebApplicationContext) this.context) + .addFilters((FilterChainProxy) this.context + .getBean("springSecurityFilterChain", Filter.class)) + .build(); + mockMvc.perform(MockMvcRequestBuilders.get("/")) + .andExpect(MockMvcResultMatchers.header().string("Content-Security-Policy", + is("default-src 'self';"))) + .andExpect(MockMvcResultMatchers.header().doesNotExist("Content-Security-Policy-Report-Only")); + } + + @Test + public void contentSecurityPolicyReportOnlyConfiguration() throws Exception { + this.context = SpringApplication.run(VanillaWebConfiguration.class, + "--security.headers.content-security-policy=default-src 'self';", + "--security.headers.content-security-policy-mode=report-only"); + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup((WebApplicationContext) this.context) + .addFilters((FilterChainProxy) this.context + .getBean("springSecurityFilterChain", Filter.class)) + .build(); + mockMvc.perform(MockMvcRequestBuilders.get("/")) + .andExpect(MockMvcResultMatchers.header().string("Content-Security-Policy-Report-Only", + is("default-src 'self';"))) + .andExpect(MockMvcResultMatchers.header().doesNotExist("Content-Security-Policy")); + } + @Configuration @Import(TestWebConfiguration.class) @Order(Ordered.LOWEST_PRECEDENCE) diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 12fbb18348..c80d4c405a 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -431,6 +431,8 @@ content into your application; rather pick only the properties that you need. security.filter-order=0 # Security filter chain order. security.filter-dispatcher-types=ASYNC, FORWARD, INCLUDE, REQUEST # Security filter chain dispatcher types. security.headers.cache=true # Enable cache control HTTP headers. + security.headers.content-security-policy= # Value for content security policy header. + security.headers.content-security-policy-mode=default # Content security policy mode (default, report-only). security.headers.content-type=true # Enable "X-Content-Type-Options" header. security.headers.frame=true # Enable "X-Frame-Options" header. security.headers.hsts= # HTTP Strict Transport Security (HSTS) mode (none, domain, all). From 238c22cd737f62b39bf98dde5752a517182bfaa8 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 16 Nov 2016 14:27:33 -0800 Subject: [PATCH 6/8] Polish content security policy contribution See gh-7373 See gh-7373 --- .../security/SecurityProperties.java | 11 +++++++---- .../SpringBootWebSecurityConfiguration.java | 10 ++++++---- ...SpringBootWebSecurityConfigurationTests.java | 17 ++++++++++------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java index 97d2ad4795..e157173a7f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java @@ -175,15 +175,17 @@ public class SecurityProperties implements SecurityPrerequisite { } public enum ContentSecurityPolicyMode { + /** - * Use the {@code Content-Security-Policy} header. + * Use the 'Content-Security-Policy' header. */ DEFAULT, /** - * Use the {@code Content-Security-Policy-Report-Only} header. + * Use the 'Content-Security-Policy-Report-Only' header. */ REPORT_ONLY + } /** @@ -212,7 +214,7 @@ public class SecurityProperties implements SecurityPrerequisite { private String contentSecurityPolicy; /** - * Whether to use the "Content-Security-Policy" or "Content-Security-Policy-Report-Only" header. + * Security policy mode. */ private ContentSecurityPolicyMode contentSecurityPolicyMode = ContentSecurityPolicyMode.DEFAULT; @@ -265,7 +267,8 @@ public class SecurityProperties implements SecurityPrerequisite { return this.contentSecurityPolicyMode; } - public void setContentSecurityPolicyMode(ContentSecurityPolicyMode contentSecurityPolicyMode) { + public void setContentSecurityPolicyMode( + ContentSecurityPolicyMode contentSecurityPolicyMode) { this.contentSecurityPolicyMode = contentSecurityPolicyMode; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java index daa5e3569a..0197ca2ee7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers; +import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers.ContentSecurityPolicyMode; import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -109,12 +110,13 @@ public class SpringBootWebSecurityConfiguration { configurer.contentTypeOptions().disable(); } if (StringUtils.hasText(headers.getContentSecurityPolicy())) { - if (headers.getContentSecurityPolicyMode() == Headers.ContentSecurityPolicyMode.DEFAULT) { - configurer.contentSecurityPolicy(headers.getContentSecurityPolicy()); + String policyDirectives = headers.getContentSecurityPolicy(); + ContentSecurityPolicyMode mode = headers.getContentSecurityPolicyMode(); + if (mode == ContentSecurityPolicyMode.DEFAULT) { + configurer.contentSecurityPolicy(policyDirectives); } else { - assert headers.getContentSecurityPolicyMode() == Headers.ContentSecurityPolicyMode.REPORT_ONLY; - configurer.contentSecurityPolicy(headers.getContentSecurityPolicy()).reportOnly(); + configurer.contentSecurityPolicy(policyDirectives).reportOnly(); } } if (!headers.isXss()) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java index 23af97a970..e20cbb74d9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java @@ -214,7 +214,8 @@ public class SpringBootWebSecurityConfigurationTests { is(notNullValue()))) .andExpect(MockMvcResultMatchers.header().string("X-Frame-Options", is(notNullValue()))) - .andExpect(MockMvcResultMatchers.header().doesNotExist("Content-Security-Policy")); + .andExpect(MockMvcResultMatchers.header() + .doesNotExist("Content-Security-Policy")); } @Test @@ -250,9 +251,10 @@ public class SpringBootWebSecurityConfigurationTests { .getBean("springSecurityFilterChain", Filter.class)) .build(); mockMvc.perform(MockMvcRequestBuilders.get("/")) - .andExpect(MockMvcResultMatchers.header().string("Content-Security-Policy", - is("default-src 'self';"))) - .andExpect(MockMvcResultMatchers.header().doesNotExist("Content-Security-Policy-Report-Only")); + .andExpect(MockMvcResultMatchers.header() + .string("Content-Security-Policy", is("default-src 'self';"))) + .andExpect(MockMvcResultMatchers.header() + .doesNotExist("Content-Security-Policy-Report-Only")); } @Test @@ -266,9 +268,10 @@ public class SpringBootWebSecurityConfigurationTests { .getBean("springSecurityFilterChain", Filter.class)) .build(); mockMvc.perform(MockMvcRequestBuilders.get("/")) - .andExpect(MockMvcResultMatchers.header().string("Content-Security-Policy-Report-Only", - is("default-src 'self';"))) - .andExpect(MockMvcResultMatchers.header().doesNotExist("Content-Security-Policy")); + .andExpect(MockMvcResultMatchers.header().string( + "Content-Security-Policy-Report-Only", is("default-src 'self';"))) + .andExpect(MockMvcResultMatchers.header() + .doesNotExist("Content-Security-Policy")); } @Configuration From 34fb909b411fb832eaaf8bb266f3f6e569e20c1e Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Mon, 7 Nov 2016 13:43:32 -0500 Subject: [PATCH 7/8] Support multiple root contexts in `Restarter` Update devtools restarter to support multiple application contexts. Fixes gh-7335 Closes gh-7336 --- .../restart/RestartApplicationListener.java | 2 +- .../boot/devtools/restart/Restarter.java | 20 ++++++++++++++----- .../RestartApplicationListenerTests.java | 10 ++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java index 8cd7d81229..2966ac1456 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java @@ -52,7 +52,7 @@ public class RestartApplicationListener || event instanceof ApplicationFailedEvent) { Restarter.getInstance().finish(); if (event instanceof ApplicationFailedEvent) { - Restarter.getInstance().prepare(null); + Restarter.getInstance().remove(((ApplicationFailedEvent) event).getApplicationContext()); } } } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 1c94bc5506..35b4b937c4 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingDeque; import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.Lock; @@ -51,6 +52,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; /** @@ -116,7 +118,7 @@ public class Restarter { private boolean finished = false; - private volatile ConfigurableApplicationContext rootContext; + private final List rootContexts = new CopyOnWriteArrayList(); /** * Internal constructor to create a new {@link Restarter} instance. @@ -314,9 +316,11 @@ public class Restarter { this.logger.debug("Stopping application"); this.stopLock.lock(); try { - if (this.rootContext != null) { - this.rootContext.close(); - this.rootContext = null; + if (!CollectionUtils.isEmpty(this.rootContexts)) { + for (ConfigurableApplicationContext rootContext : this.rootContexts) { + rootContext.close(); + } + this.rootContexts.clear(); } cleanupCaches(); if (this.forceReferenceCleanup) { @@ -418,7 +422,13 @@ public class Restarter { if (applicationContext != null && applicationContext.getParent() != null) { return; } - this.rootContext = applicationContext; + this.rootContexts.add(applicationContext); + } + + void remove(ConfigurableApplicationContext applicationContext) { + if (applicationContext != null) { + this.rootContexts.remove(applicationContext); + } } private LeakSafeThread getLeakSafeThread() { diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java index 31acad0f0e..34848adbc2 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.devtools.restart; +import java.util.List; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -64,8 +66,8 @@ public class RestartApplicationListenerTests { assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "args")) .isEqualTo(ARGS); assertThat(Restarter.getInstance().isFinished()).isTrue(); - assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "rootContext")) - .isNotNull(); + assertThat((List) ReflectionTestUtils.getField(Restarter.getInstance(), + "rootContexts")).isNotEmpty(); } @Test @@ -74,8 +76,8 @@ public class RestartApplicationListenerTests { assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "args")) .isEqualTo(ARGS); assertThat(Restarter.getInstance().isFinished()).isTrue(); - assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "rootContext")) - .isNull(); + assertThat((List) ReflectionTestUtils.getField(Restarter.getInstance(), + "rootContexts")).isEmpty(); } @Test From dfd327d68fca364b3267db63da8a504a4c0e65a6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 16 Nov 2016 15:17:23 -0800 Subject: [PATCH 8/8] Polish multiple root contexts in `Restarter` See gh-7335 See gh-7336 --- .../restart/RestartApplicationListener.java | 17 ++++++++++++----- .../boot/devtools/restart/Restarter.java | 9 +++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java index 2966ac1456..817d173359 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java @@ -45,15 +45,14 @@ public class RestartApplicationListener onApplicationStartingEvent((ApplicationStartingEvent) event); } if (event instanceof ApplicationPreparedEvent) { - Restarter.getInstance() - .prepare(((ApplicationPreparedEvent) event).getApplicationContext()); + onApplicationPreparedEvent((ApplicationPreparedEvent) event); } if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) { Restarter.getInstance().finish(); - if (event instanceof ApplicationFailedEvent) { - Restarter.getInstance().remove(((ApplicationFailedEvent) event).getApplicationContext()); - } + } + if (event instanceof ApplicationFailedEvent) { + onApplicationFailedEvent((ApplicationFailedEvent) event); } } @@ -72,6 +71,14 @@ public class RestartApplicationListener } } + private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { + Restarter.getInstance().prepare(event.getApplicationContext()); + } + + private void onApplicationFailedEvent(ApplicationFailedEvent event) { + Restarter.getInstance().remove(event.getApplicationContext()); + } + @Override public int getOrder() { return this.order; diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 35b4b937c4..681026c032 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -52,7 +52,6 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; /** @@ -316,11 +315,9 @@ public class Restarter { this.logger.debug("Stopping application"); this.stopLock.lock(); try { - if (!CollectionUtils.isEmpty(this.rootContexts)) { - for (ConfigurableApplicationContext rootContext : this.rootContexts) { - rootContext.close(); - } - this.rootContexts.clear(); + for (ConfigurableApplicationContext context : this.rootContexts) { + context.close(); + this.rootContexts.remove(context); } cleanupCaches(); if (this.forceReferenceCleanup) {