Fix handling of security.headers.* to allow headers to be disabled

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
pull/4160/merge
Andy Wilkinson 9 years ago
parent f4c1efd128
commit 25e719f549

@ -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).

@ -75,6 +75,7 @@ import org.springframework.util.StringUtils;
* </ul>
*
* @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();
}
}

@ -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)

@ -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

Loading…
Cancel
Save