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
This commit is contained in:
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…
Reference in New Issue