From 25e719f549097d5a9e0d3561aa492c5d69a93e34 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 13 Oct 2015 15:43:57 +0100 Subject: [PATCH] Fix handling of security.headers.* to allow headers to be disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spring Security 4’s default configuration will, irrespective of any other header writers that are added, enable writers for the following headers: - X-Content-Type - X-XSS-Protection - Cache-Control - X-Frame-Options Previously, SecurityProperties.headers used false as the default for the properties that enable or disable these headers but the configuration is only applied when the properties are true. This left us with the right default behaviour (the headers are enabled) but meant that the properties could not be used to switch off the headers. This commit changes the defaults for the four properties to true and updates SpringBootWebSecurityConfiguration to only apply the configuration when the properties are false. This leaves us with the desired defaults while allowing users to disable one or more of the properties by setting the relevant property to false. Closes gh-3517 --- .../security/SecurityProperties.java | 9 ++-- .../SpringBootWebSecurityConfiguration.java | 17 +++---- ...ringBootWebSecurityConfigurationTests.java | 47 +++++++++++++++++++ .../appendix-application-properties.adoc | 8 ++-- 4 files changed, 65 insertions(+), 16 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 89d4ec5845..30998a4c30 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 @@ -31,6 +31,7 @@ import org.springframework.util.StringUtils; * Properties for the security aspects of an application. * * @author Dave Syer + * @author Andy Wilkinson */ @ConfigurationProperties(prefix = "security") public class SecurityProperties implements SecurityPrerequisite { @@ -162,22 +163,22 @@ public class SecurityProperties implements SecurityPrerequisite { /** * Enable cross site scripting (XSS) protection. */ - private boolean xss; + private boolean xss = true; /** * Enable cache control HTTP headers. */ - private boolean cache; + private boolean cache = true; /** * Enable "X-Frame-Options" header. */ - private boolean frame; + private boolean frame = true; /** * Enable "X-Content-Type-Options" header. */ - private boolean contentType; + private boolean contentType = true; /** * HTTP Strict Transport Security (HSTS) mode (none, domain, all). 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 f9d2d2d57d..2d6fa77e1c 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 @@ -75,6 +75,7 @@ import org.springframework.util.StringUtils; * * * @author Dave Syer + * @author Andy Wilkinson */ @Configuration @EnableConfigurationProperties @@ -101,17 +102,17 @@ public class SpringBootWebSecurityConfiguration { writer.setRequestMatcher(AnyRequestMatcher.INSTANCE); configurer.addHeaderWriter(writer); } - if (headers.isContentType()) { - configurer.contentTypeOptions(); + if (!headers.isContentType()) { + configurer.contentTypeOptions().disable(); } - if (headers.isXss()) { - configurer.xssProtection(); + if (!headers.isXss()) { + configurer.xssProtection().disable(); } - if (headers.isCache()) { - configurer.cacheControl(); + if (!headers.isCache()) { + configurer.cacheControl().disable(); } - if (headers.isFrame()) { - configurer.frameOptions(); + if (!headers.isFrame()) { + configurer.frameOptions().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 bd575f19b3..cb7f8c2861 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 @@ -51,6 +51,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.FilterChainProxy; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -60,6 +61,8 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; import static org.junit.Assert.assertEquals; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -68,6 +71,7 @@ import static org.junit.Assert.assertTrue; * * @author Dave Syer * @author Rob Winch + * @author Andy Wilkinson */ public class SpringBootWebSecurityConfigurationTests { @@ -189,6 +193,49 @@ public class SpringBootWebSecurityConfigurationTests { assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); } + @Test + public void defaultHeaderConfiguration() throws Exception { + this.context = SpringApplication.run(VanillaWebConfiguration.class, + "--server.port=0"); + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup((WebApplicationContext) this.context) + .addFilters((FilterChainProxy) this.context + .getBean("springSecurityFilterChain", Filter.class)) + .build(); + mockMvc.perform(MockMvcRequestBuilders.get("/")) + .andExpect(MockMvcResultMatchers.header().string("X-Content-Type-Options", + is(notNullValue()))) + .andExpect(MockMvcResultMatchers.header().string("X-XSS-Protection", + is(notNullValue()))) + .andExpect(MockMvcResultMatchers.header().string("Cache-Control", + is(notNullValue()))) + .andExpect(MockMvcResultMatchers.header().string("X-Frame-Options", + is(notNullValue()))); + } + + @Test + public void securityHeadersCanBeDisabled() throws Exception { + this.context = SpringApplication.run(VanillaWebConfiguration.class, + "--server.port=0", "--security.headers.content-type=false", + "--security.headers.xss=false", "--security.headers.cache=false", + "--security.headers.frame=false"); + + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup((WebApplicationContext) this.context) + .addFilters( + this.context.getBean("springSecurityFilterChain", Filter.class)) + .build(); + mockMvc.perform(MockMvcRequestBuilders.get("/")) + .andExpect(MockMvcResultMatchers.status().isUnauthorized()) + .andExpect(MockMvcResultMatchers.header() + .doesNotExist("X-Content-Type-Options")) + .andExpect( + MockMvcResultMatchers.header().doesNotExist("X-XSS-Protection")) + .andExpect(MockMvcResultMatchers.header().doesNotExist("Cache-Control")) + .andExpect( + MockMvcResultMatchers.header().doesNotExist("X-Frame-Options")); + } + @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 fcd6e79f90..a890d53700 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -303,10 +303,10 @@ content into your application; rather pick only the properties that you need. security.basic.path= # /** security.basic.authorize-mode= # ROLE, AUTHENTICATED, NONE security.filter-order=0 - security.headers.xss=false - security.headers.cache=false - security.headers.frame=false - security.headers.content-type=false + security.headers.xss=true + security.headers.cache=true + security.headers.frame=true + security.headers.content-type=true security.headers.hsts=all # none / domain / all security.sessions=stateless # always / never / if_required / stateless security.ignored= # Comma-separated list of paths to exclude from the default secured paths