SEC-2846: Security HTTP Response Headers Configuration Cleanup
This commit is contained in:
parent
9c5cb2f438
commit
c4fe630f8e
|
@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web.configurers;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
@ -28,80 +30,50 @@ import org.springframework.security.web.header.writers.HstsHeaderWriter;
|
|||
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Adds the Security headers to the response. This is activated by default when
|
||||
* using {@link WebSecurityConfigurerAdapter}'s default constructor. Only
|
||||
* invoking the {@link #headers()} without invoking additional methods on it, or
|
||||
* accepting the default provided by {@link WebSecurityConfigurerAdapter}, is
|
||||
* the equivalent of:
|
||||
* <p>
|
||||
* Adds the Security HTTP headers to the response. Security HTTP headers is
|
||||
* activated by default when using {@link WebSecurityConfigurerAdapter}'s
|
||||
* default constructor.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The default headers are include are:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .headers()
|
||||
* .contentTypeOptions();
|
||||
* .xssProtection()
|
||||
* .cacheControl()
|
||||
* .httpStrictTransportSecurity()
|
||||
* .frameOptions()
|
||||
* .and()
|
||||
* ...;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* You can disable the headers using the following:
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .headers().disable()
|
||||
* ...;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* You can enable only a few of the headers by invoking the appropriate methods
|
||||
* on {@link #headers()} result. For example, the following will enable
|
||||
* {@link HeadersConfigurer#cacheControl()} and
|
||||
* {@link HeadersConfigurer#frameOptions()} only.
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .headers()
|
||||
* .cacheControl()
|
||||
* .frameOptions()
|
||||
* .and()
|
||||
* ...;
|
||||
* }
|
||||
* }
|
||||
* Cache-Control: no-cache, no-store, max-age=0, must-revalidate
|
||||
* Pragma: no-cache
|
||||
* Expires: 0
|
||||
* X-Content-Type-Options: nosniff
|
||||
* Strict-Transport-Security: max-age=31536000 ; includeSubDomains
|
||||
* X-Frame-Options: DENY
|
||||
* X-XSS-Protection: 1; mode=block
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
||||
public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
||||
AbstractHttpConfigurer<HeadersConfigurer<H>, H> {
|
||||
private List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>();
|
||||
|
||||
// --- default header writers ---
|
||||
|
||||
private final ContentTypeOptionsConfig contentTypeOptions = new ContentTypeOptionsConfig();
|
||||
|
||||
private final XXssConfig xssProtection = new XXssConfig();
|
||||
|
||||
private final CacheControlConfig cacheControl = new CacheControlConfig();
|
||||
|
||||
private final HstsConfig hsts = new HstsConfig();
|
||||
|
||||
private final FrameOptionsConfig frameOptions = new FrameOptionsConfig();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
|
@ -124,7 +96,7 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds {@link XContentTypeOptionsHeaderWriter} which inserts the <a href=
|
||||
* Configures the {@link XContentTypeOptionsHeaderWriter} which inserts the <a href=
|
||||
* "http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx"
|
||||
* >X-Content-Type-Options</a>:
|
||||
*
|
||||
|
@ -132,27 +104,146 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
* X-Content-Type-Options: nosniff
|
||||
* </pre>
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @return the ContentTypeOptionsConfig for additional customizations
|
||||
*/
|
||||
public HeadersConfigurer<H> contentTypeOptions() {
|
||||
return addHeaderWriter(new XContentTypeOptionsHeaderWriter());
|
||||
public ContentTypeOptionsConfig contentTypeOptions() {
|
||||
return contentTypeOptions.enable();
|
||||
}
|
||||
|
||||
public final class ContentTypeOptionsConfig {
|
||||
private XContentTypeOptionsHeaderWriter writer;
|
||||
|
||||
private ContentTypeOptionsConfig() {
|
||||
enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the X-XSS-Protection header.
|
||||
*
|
||||
* @return {@link HeadersConfigurer} for additional customization.
|
||||
*/
|
||||
public HeadersConfigurer<H> disable() {
|
||||
writer = null;
|
||||
return and();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the {@link HeadersConfigurer}
|
||||
* @return the {@link HeadersConfigurer} for additional customization
|
||||
*/
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that Content Type Options is enabled
|
||||
*
|
||||
* @return the {@link ContentTypeOptionsConfig} for additional customization
|
||||
*/
|
||||
private ContentTypeOptionsConfig enable() {
|
||||
if(writer == null) {
|
||||
writer = new XContentTypeOptionsHeaderWriter();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <strong>Note this is not comprehensive XSS protection!</strong>
|
||||
*
|
||||
* <para>Adds {@link XXssProtectionHeaderWriter} which adds the <a href=
|
||||
* <p>Allows customizing the {@link XXssProtectionHeaderWriter} which adds the <a href=
|
||||
* "http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx"
|
||||
* >X-XSS-Protection header</a>
|
||||
* >X-XSS-Protection header</a></p>
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
*/
|
||||
public HeadersConfigurer<H> xssProtection() {
|
||||
return addHeaderWriter(new XXssProtectionHeaderWriter());
|
||||
public XXssConfig xssProtection() {
|
||||
return xssProtection.enable();
|
||||
}
|
||||
|
||||
public final class XXssConfig {
|
||||
private XXssProtectionHeaderWriter writer;
|
||||
|
||||
private XXssConfig() {
|
||||
enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* If false, will not specify the mode as blocked. In this instance, any
|
||||
* content will be attempted to be fixed. If true, the content will be
|
||||
* replaced with "#".
|
||||
*
|
||||
* @param enabled
|
||||
* the new value
|
||||
*/
|
||||
public XXssConfig block(boolean enabled) {
|
||||
writer.setBlock(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the header value will contain a value of 1. For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1
|
||||
* </pre>
|
||||
*
|
||||
* or if {@link #setBlock(boolean)} is true
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1; mode=block
|
||||
* </pre>
|
||||
*
|
||||
* If false, will explicitly disable specify that X-XSS-Protection is
|
||||
* disabled. For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 0
|
||||
* </pre>
|
||||
*
|
||||
* @param enabled the new value
|
||||
*/
|
||||
public XXssConfig xssProtectionEnabled(boolean enabled) {
|
||||
writer.setEnabled(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables X-XSS-Protection header (does not include it)
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> disable() {
|
||||
writer = null;
|
||||
return and();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows completing configuration of Strict Transport Security and
|
||||
* continuing configuration of headers.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the X-XSS-Protection header is enabled if it is not already.
|
||||
*
|
||||
* @return the {@link XXssConfig} for additional customization
|
||||
*/
|
||||
private XXssConfig enable() {
|
||||
if(writer == null) {
|
||||
writer = new XXssProtectionHeaderWriter();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link CacheControlHeadersWriter}. Specifically it adds the
|
||||
* Allows customizing the {@link CacheControlHeadersWriter}. Specifically it adds the
|
||||
* following headers:
|
||||
* <ul>
|
||||
* <li>Cache-Control: no-cache, no-store, max-age=0, must-revalidate</li>
|
||||
|
@ -162,37 +253,252 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
*/
|
||||
public HeadersConfigurer<H> cacheControl() {
|
||||
return addHeaderWriter(new CacheControlHeadersWriter());
|
||||
public CacheControlConfig cacheControl() {
|
||||
return cacheControl.enable();
|
||||
}
|
||||
|
||||
public final class CacheControlConfig {
|
||||
private CacheControlHeadersWriter writer;
|
||||
|
||||
private CacheControlConfig() {
|
||||
enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables Cache Control
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> disable() {
|
||||
writer = null;
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows completing configuration of Strict Transport Security and
|
||||
* continuing configuration of headers.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the Cache Control headers are enabled if they are not already.
|
||||
*
|
||||
* @return the {@link CacheControlConfig} for additional customization
|
||||
*/
|
||||
private CacheControlConfig enable() {
|
||||
if(writer == null) {
|
||||
writer = new CacheControlHeadersWriter();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link HstsHeaderWriter} which provides support for <a
|
||||
* Allows customizing the {@link HstsHeaderWriter} which provides support for <a
|
||||
* href="http://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security
|
||||
* (HSTS)</a>.
|
||||
*
|
||||
* <p>
|
||||
* For additional configuration options, use
|
||||
* {@link #addHeaderWriter(HeaderWriter)} and {@link HstsHeaderWriter}
|
||||
* directly.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
*/
|
||||
public HeadersConfigurer<H> httpStrictTransportSecurity() {
|
||||
return addHeaderWriter(new HstsHeaderWriter());
|
||||
public HstsConfig httpStrictTransportSecurity() {
|
||||
return hsts.enable();
|
||||
}
|
||||
|
||||
public final class HstsConfig {
|
||||
private HstsHeaderWriter writer;
|
||||
|
||||
private HstsConfig() {
|
||||
enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the value (in seconds) for the max-age directive of the
|
||||
* Strict-Transport-Security header. The default is one year.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This instructs browsers how long to remember to keep this domain as a
|
||||
* known HSTS Host. See <a
|
||||
* href="http://tools.ietf.org/html/rfc6797#section-6.1.1">Section 6.1.1</a>
|
||||
* for additional details.
|
||||
* </p>
|
||||
*
|
||||
* @param maxAgeInSeconds
|
||||
* the maximum amount of time (in seconds) to consider this
|
||||
* domain as a known HSTS Host.
|
||||
* @throws IllegalArgumentException
|
||||
* if maxAgeInSeconds is negative
|
||||
*/
|
||||
public HstsConfig maxAgeInSeconds(long maxAgeInSeconds) {
|
||||
writer.setMaxAgeInSeconds(maxAgeInSeconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RequestMatcher} used to determine if the
|
||||
* "Strict-Transport-Security" should be added. If true the header is added,
|
||||
* else the header is not added. By default the header is added when
|
||||
* {@link HttpServletRequest#isSecure()} returns true.
|
||||
*
|
||||
* @param requestMatcher
|
||||
* the {@link RequestMatcher} to use.
|
||||
* @throws IllegalArgumentException
|
||||
* if {@link RequestMatcher} is null
|
||||
*/
|
||||
public HstsConfig requestMatcher(RequestMatcher requestMatcher) {
|
||||
writer.setRequestMatcher(requestMatcher);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* If true, subdomains should be considered HSTS Hosts too. The default is
|
||||
* true.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* See <a href="http://tools.ietf.org/html/rfc6797#section-6.1.2">Section
|
||||
* 6.1.2</a> for additional details.
|
||||
* </p>
|
||||
*
|
||||
* @param includeSubDomains
|
||||
* true to include subdomains, else false
|
||||
*/
|
||||
public HstsConfig includeSubDomains(boolean includeSubDomains) {
|
||||
writer.setIncludeSubDomains(includeSubDomains);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables Strict Transport Security
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> disable() {
|
||||
writer = null;
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows completing configuration of Strict Transport Security and
|
||||
* continuing configuration of headers.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that Strict-Transport-Security is enabled if it is not already
|
||||
*
|
||||
* @return the {@link HstsConfig} for additional customization
|
||||
*/
|
||||
private HstsConfig enable() {
|
||||
if(writer == null) {
|
||||
writer = new HstsHeaderWriter();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link XFrameOptionsHeaderWriter} with all the default settings. For
|
||||
* additional configuration options, use
|
||||
* {@link #addHeaderWriter(HeaderWriter)} and
|
||||
* {@link XFrameOptionsHeaderWriter} directly.
|
||||
* Allows customizing the {@link XFrameOptionsHeaderWriter}.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
*/
|
||||
public HeadersConfigurer<H> frameOptions() {
|
||||
return addHeaderWriter(new XFrameOptionsHeaderWriter());
|
||||
public FrameOptionsConfig frameOptions() {
|
||||
return frameOptions.enable();
|
||||
}
|
||||
|
||||
public final class FrameOptionsConfig {
|
||||
private XFrameOptionsHeaderWriter writer;
|
||||
|
||||
private FrameOptionsConfig() {
|
||||
enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify to DENY framing any content from this application.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customization.
|
||||
*/
|
||||
public HeadersConfigurer<H> deny() {
|
||||
writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.DENY);
|
||||
return and();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Specify to allow any request that comes from the same origin to frame
|
||||
* this application. For example, if the application was hosted on
|
||||
* example.com, then example.com could frame the application, but
|
||||
* evil.com could not frame the application.
|
||||
* </p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public HeadersConfigurer<H> sameOrigin() {
|
||||
writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN);
|
||||
return and();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents the header from being added to the response.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration.
|
||||
*/
|
||||
public HeadersConfigurer<H> disable() {
|
||||
writer = null;
|
||||
return and();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows continuing customizing the headers configuration.
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
*/
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables FrameOptionsConfig if it is not already enabled.
|
||||
*
|
||||
* @return the FrameOptionsConfig for additional customization.
|
||||
*/
|
||||
private FrameOptionsConfig enable() {
|
||||
if(writer == null) {
|
||||
writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.DENY);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all of the default headers from the response. After doing so, one can add headers back. For example, if you only want to use Spring Security's cache control you can use the following:
|
||||
*
|
||||
* <pre>
|
||||
* http
|
||||
* .headers()
|
||||
* .defaultsDisabled()
|
||||
* .cacheControl();
|
||||
* </pre>
|
||||
*
|
||||
* @return the {@link HeadersConfigurer} for additional customization
|
||||
*/
|
||||
public HeadersConfigurer<H> defaultsDisabled() {
|
||||
contentTypeOptions.disable();
|
||||
xssProtection.disable();
|
||||
cacheControl.disable();
|
||||
hsts.disable();
|
||||
frameOptions.disable();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -207,8 +513,12 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
* @return the {@link HeaderWriter}
|
||||
*/
|
||||
private HeaderWriterFilter createHeaderWriterFilter() {
|
||||
List<HeaderWriter> writers = getHeaderWriters();
|
||||
if(writers.isEmpty()) {
|
||||
throw new IllegalStateException("Headers security is enabled, but no headers will be added. Either add headers or disable headers security");
|
||||
}
|
||||
HeaderWriterFilter headersFilter = new HeaderWriterFilter(
|
||||
getHeaderWriters());
|
||||
writers);
|
||||
headersFilter = postProcess(headersFilter);
|
||||
return headersFilter;
|
||||
}
|
||||
|
@ -220,23 +530,19 @@ public final class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
* @return
|
||||
*/
|
||||
private List<HeaderWriter> getHeaderWriters() {
|
||||
if (headerWriters.isEmpty()) {
|
||||
addDefaultHeaderWriters();
|
||||
}
|
||||
return headerWriters;
|
||||
List<HeaderWriter> writers = new ArrayList<HeaderWriter>();
|
||||
addIfNotNull(writers, contentTypeOptions.writer);
|
||||
addIfNotNull(writers, xssProtection.writer);
|
||||
addIfNotNull(writers, cacheControl.writer);
|
||||
addIfNotNull(writers, hsts.writer);
|
||||
addIfNotNull(writers, frameOptions.writer);
|
||||
writers.addAll(headerWriters);
|
||||
return writers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly adds the default {@link HeaderWriter} instances. If no,
|
||||
* {@link HeaderWriter} instances have been added this is automatically
|
||||
* invoked.
|
||||
*
|
||||
*/
|
||||
private void addDefaultHeaderWriters() {
|
||||
contentTypeOptions();
|
||||
xssProtection();
|
||||
cacheControl();
|
||||
httpStrictTransportSecurity();
|
||||
frameOptions();
|
||||
private <T> void addIfNotNull(List<T> values, T value) {
|
||||
if(value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ package org.springframework.security.config.http;
|
|||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
|
@ -47,6 +48,7 @@ import org.w3c.dom.Element;
|
|||
* @since 3.2
|
||||
*/
|
||||
public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
||||
private static final String ATT_DISABLED = "disabled";
|
||||
|
||||
private static final String ATT_ENABLED = "enabled";
|
||||
private static final String ATT_BLOCK = "block";
|
||||
|
@ -80,43 +82,37 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
headerWriters = new ManagedList<BeanMetadataElement>();
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeaderWriterFilter.class);
|
||||
|
||||
if(element != null) {
|
||||
|
||||
parseCacheControlElement(element);
|
||||
parseHstsElement(element);
|
||||
parseXssElement(element, parserContext);
|
||||
parseFrameOptionsElement(element, parserContext);
|
||||
parseContentTypeOptionsElement(element);
|
||||
|
||||
parseHeaderElements(element);
|
||||
}
|
||||
|
||||
boolean disabled = element != null && "true".equals(element.getAttribute("disabled"));
|
||||
boolean defaultsDisabled = element != null && "true".equals(element.getAttribute("defaults-disabled"));
|
||||
|
||||
boolean addIfNotPresent = element == null || !disabled && !defaultsDisabled;
|
||||
|
||||
parseCacheControlElement(addIfNotPresent, element);
|
||||
parseHstsElement(addIfNotPresent,element, parserContext);
|
||||
parseXssElement(addIfNotPresent, element, parserContext);
|
||||
parseFrameOptionsElement(addIfNotPresent, element, parserContext);
|
||||
parseContentTypeOptionsElement(addIfNotPresent, element);
|
||||
|
||||
parseHeaderElements(element);
|
||||
|
||||
if(disabled) {
|
||||
if(!headerWriters.isEmpty()) {
|
||||
parserContext.getReaderContext().error("Cannot specify <headers disabled=\"true\"> with child elements.", element);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if(headerWriters.isEmpty()) {
|
||||
addCacheControl();
|
||||
addHsts(null);
|
||||
addContentTypeOptions();
|
||||
|
||||
BeanDefinitionBuilder frameOptions = BeanDefinitionBuilder.genericBeanDefinition(XFrameOptionsHeaderWriter.class);
|
||||
frameOptions.addConstructorArgValue("DENY");
|
||||
headerWriters.add(frameOptions.getBeanDefinition());
|
||||
|
||||
BeanDefinitionBuilder xss = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class);
|
||||
headerWriters.add(xss.getBeanDefinition());
|
||||
}
|
||||
builder.addConstructorArgValue(headerWriters);
|
||||
return builder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private void parseCacheControlElement(Element element) {
|
||||
Element cacheControlElement = DomUtils.getChildElementByTagName(element, CACHE_CONTROL_ELEMENT);
|
||||
if (cacheControlElement != null) {
|
||||
private void parseCacheControlElement(boolean addIfNotPresent, Element element) {
|
||||
Element cacheControlElement = element == null ? null : DomUtils.getChildElementByTagName(element, CACHE_CONTROL_ELEMENT);
|
||||
boolean disabled = "true".equals(getAttribute(cacheControlElement, ATT_DISABLED, "false"));
|
||||
if(disabled) {
|
||||
return;
|
||||
}
|
||||
if (addIfNotPresent || cacheControlElement != null) {
|
||||
addCacheControl();
|
||||
}
|
||||
}
|
||||
|
@ -126,34 +122,55 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
headerWriters.add(headersWriter.getBeanDefinition());
|
||||
}
|
||||
|
||||
private void parseHstsElement(Element element) {
|
||||
Element hstsElement = DomUtils.getChildElementByTagName(element, HSTS_ELEMENT);
|
||||
if (hstsElement != null) {
|
||||
addHsts(hstsElement);
|
||||
private void parseHstsElement(boolean addIfNotPresent, Element element, ParserContext context) {
|
||||
Element hstsElement = element == null ? null : DomUtils.getChildElementByTagName(element, HSTS_ELEMENT);
|
||||
if (addIfNotPresent || hstsElement != null) {
|
||||
addHsts(addIfNotPresent, hstsElement, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHsts(Element hstsElement) {
|
||||
private void addHsts(boolean addIfNotPresent, Element hstsElement, ParserContext context) {
|
||||
BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder.genericBeanDefinition(HstsHeaderWriter.class);
|
||||
if(hstsElement != null) {
|
||||
boolean disabled = "true".equals(getAttribute(hstsElement, ATT_DISABLED, "false"));
|
||||
String includeSubDomains = hstsElement.getAttribute(ATT_INCLUDE_SUBDOMAINS);
|
||||
if(StringUtils.hasText(includeSubDomains)) {
|
||||
if(disabled) {
|
||||
attrNotAllowed(context, ATT_INCLUDE_SUBDOMAINS, ATT_DISABLED, hstsElement);
|
||||
}
|
||||
headersWriter.addPropertyValue("includeSubDomains", includeSubDomains);
|
||||
}
|
||||
String maxAgeSeconds = hstsElement.getAttribute(ATT_MAX_AGE_SECONDS);
|
||||
if(StringUtils.hasText(maxAgeSeconds)) {
|
||||
if(disabled) {
|
||||
attrNotAllowed(context, ATT_MAX_AGE_SECONDS, ATT_DISABLED, hstsElement);
|
||||
}
|
||||
headersWriter.addPropertyValue("maxAgeInSeconds", maxAgeSeconds);
|
||||
}
|
||||
String requestMatcherRef = hstsElement.getAttribute(ATT_REQUEST_MATCHER_REF);
|
||||
if(StringUtils.hasText(requestMatcherRef)) {
|
||||
if(disabled) {
|
||||
attrNotAllowed(context, ATT_REQUEST_MATCHER_REF, ATT_DISABLED, hstsElement);
|
||||
}
|
||||
headersWriter.addPropertyReference("requestMatcher", requestMatcherRef);
|
||||
}
|
||||
|
||||
if(disabled == true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
headerWriters.add(headersWriter.getBeanDefinition());
|
||||
if(addIfNotPresent || hstsElement != null) {
|
||||
headerWriters.add(headersWriter.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
|
||||
context.getReaderContext().error("Only one of '" + attrName + "' or '"+ otherAttrName +"' can be set.",
|
||||
element);
|
||||
}
|
||||
|
||||
private void parseHeaderElements(Element element) {
|
||||
List<Element> headerElts = DomUtils.getChildElementsByTagName(element, GENERIC_HEADER_ELEMENT);
|
||||
List<Element> headerElts = element == null ? Collections.<Element>emptyList() : DomUtils.getChildElementsByTagName(element, GENERIC_HEADER_ELEMENT);
|
||||
for (Element headerElt : headerElts) {
|
||||
String headerFactoryRef = headerElt.getAttribute(ATT_REF);
|
||||
if (StringUtils.hasText(headerFactoryRef)) {
|
||||
|
@ -167,9 +184,13 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void parseContentTypeOptionsElement(Element element) {
|
||||
Element contentTypeElt = DomUtils.getChildElementByTagName(element, CONTENT_TYPE_ELEMENT);
|
||||
if (contentTypeElt != null) {
|
||||
private void parseContentTypeOptionsElement(boolean addIfNotPresent, Element element) {
|
||||
Element contentTypeElt = element == null ? null : DomUtils.getChildElementByTagName(element, CONTENT_TYPE_ELEMENT);
|
||||
boolean disabled = "true".equals(getAttribute(contentTypeElt, ATT_DISABLED, "false"));
|
||||
if(disabled) {
|
||||
return;
|
||||
}
|
||||
if (addIfNotPresent || contentTypeElt != null) {
|
||||
addContentTypeOptions();
|
||||
}
|
||||
}
|
||||
|
@ -179,12 +200,21 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
headerWriters.add(builder.getBeanDefinition());
|
||||
}
|
||||
|
||||
private void parseFrameOptionsElement(Element element, ParserContext parserContext) {
|
||||
private void parseFrameOptionsElement(boolean addIfNotPresent, Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XFrameOptionsHeaderWriter.class);
|
||||
|
||||
Element frameElt = DomUtils.getChildElementByTagName(element, FRAME_OPTIONS_ELEMENT);
|
||||
Element frameElt = element == null ? null : DomUtils.getChildElementByTagName(element, FRAME_OPTIONS_ELEMENT);
|
||||
if (frameElt != null) {
|
||||
String header = getAttribute(frameElt, ATT_POLICY, "DENY");
|
||||
String header = getAttribute(frameElt, ATT_POLICY, null);
|
||||
boolean disabled = "true".equals(getAttribute(frameElt, ATT_DISABLED, "false"));
|
||||
|
||||
if(disabled && header != null) {
|
||||
this.attrNotAllowed(parserContext, ATT_DISABLED, ATT_POLICY, frameElt);
|
||||
}
|
||||
if(!StringUtils.hasText(header)) {
|
||||
header = "DENY";
|
||||
}
|
||||
|
||||
if (ALLOW_FROM.equals(header) ) {
|
||||
String strategyRef = getAttribute(frameElt, ATT_REF, null);
|
||||
String strategy = getAttribute(frameElt, ATT_STRATEGY, null);
|
||||
|
@ -227,28 +257,52 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
} else {
|
||||
builder.addConstructorArgValue(header);
|
||||
}
|
||||
|
||||
if(disabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(addIfNotPresent || frameElt != null) {
|
||||
headerWriters.add(builder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseXssElement(Element element, ParserContext parserContext) {
|
||||
Element xssElt = DomUtils.getChildElementByTagName(element, XSS_ELEMENT);
|
||||
private void parseXssElement(boolean addIfNotPresent, Element element, ParserContext parserContext) {
|
||||
Element xssElt = element == null ? null : DomUtils.getChildElementByTagName(element, XSS_ELEMENT);
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class);
|
||||
if (xssElt != null) {
|
||||
String enabled = xssElt.getAttribute(ATT_ENABLED);
|
||||
String block = xssElt.getAttribute(ATT_BLOCK);
|
||||
boolean disabled = "true".equals(getAttribute(xssElt, ATT_DISABLED, "false"));
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XXssProtectionHeaderWriter.class);
|
||||
String enabled = xssElt.getAttribute(ATT_ENABLED);
|
||||
if(StringUtils.hasText(enabled)) {
|
||||
if(disabled) {
|
||||
attrNotAllowed(parserContext, ATT_ENABLED, ATT_DISABLED, xssElt);
|
||||
}
|
||||
builder.addPropertyValue("enabled", enabled);
|
||||
}
|
||||
|
||||
String block = xssElt.getAttribute(ATT_BLOCK);
|
||||
if(StringUtils.hasText(block)) {
|
||||
if(disabled) {
|
||||
attrNotAllowed(parserContext, ATT_BLOCK, ATT_DISABLED, xssElt);
|
||||
}
|
||||
builder.addPropertyValue("block", block);
|
||||
}
|
||||
|
||||
if(disabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(addIfNotPresent || xssElt != null) {
|
||||
headerWriters.add(builder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
private String getAttribute(Element element, String name, String defaultValue) {
|
||||
if(element == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
String value = element.getAttribute(name);
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value;
|
||||
|
|
|
@ -734,12 +734,18 @@ csrf-options.attlist &=
|
|||
headers =
|
||||
## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
|
||||
element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & header*)}
|
||||
headers-options.attlist &=
|
||||
## Specifies if the default headers should be disabled. Default false.
|
||||
attribute defaults-disabled {xsd:boolean}?
|
||||
headers-options.attlist &=
|
||||
## Specifies if headers should be disabled. Default false.
|
||||
attribute disabled {xsd:boolean}?
|
||||
hsts =
|
||||
## Adds support for HTTP Strict Transport Security (HSTS)
|
||||
element hsts {hsts-options.attlist}
|
||||
hsts-options.attlist &=
|
||||
## Specifies if HTTP Strict Transport Security (HSTS) should be disabled. Default false.
|
||||
attribute disabled {xsd:boolean}?
|
||||
hsts-options.attlist &=
|
||||
## Specifies if subdomains should be included. Default true.
|
||||
attribute include-subdomains {xsd:boolean}?
|
||||
|
@ -752,11 +758,17 @@ hsts-options.attlist &=
|
|||
|
||||
cache-control =
|
||||
## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
|
||||
element cache-control {empty}
|
||||
element cache-control {cache-control.attlist}
|
||||
cache-control.attlist &=
|
||||
## Specifies if Cache Control should be disabled. Default false.
|
||||
attribute disabled {xsd:boolean}?
|
||||
|
||||
frame-options =
|
||||
## Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options header.
|
||||
element frame-options {frame-options.attlist,empty}
|
||||
frame-options.attlist &=
|
||||
## If disabled, the X-Frame-Options header will not be included. Default false.
|
||||
attribute disabled {xsd:boolean}?
|
||||
frame-options.attlist &=
|
||||
## Specify the policy to use for the X-Frame-Options-Header.
|
||||
attribute policy {"DENY","SAMEORIGIN","ALLOW-FROM"}?
|
||||
|
@ -778,7 +790,10 @@ xss-protection =
|
|||
## Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the X-XSS-Protection header.
|
||||
element xss-protection {xss-protection.attlist,empty}
|
||||
xss-protection.attlist &=
|
||||
## enable or disable the X-XSS-Protection header. Default is 'true' meaning it is enabled.
|
||||
## disable the X-XSS-Protection header. Default is 'false' meaning it is enabled.
|
||||
attribute disabled {xsd:boolean}?
|
||||
xss-protection.attlist &=
|
||||
## specify that XSS Protection should be explicitly enabled or disabled. Default is 'true' meaning it is enabled.
|
||||
attribute enabled {xsd:boolean}?
|
||||
xss-protection.attlist &=
|
||||
## Add mode=block to the header or not, default is on.
|
||||
|
@ -786,7 +801,10 @@ xss-protection.attlist &=
|
|||
|
||||
content-type-options =
|
||||
## Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'.
|
||||
element content-type-options {empty}
|
||||
element content-type-options {content-type-options.attlist, empty}
|
||||
content-type-options.attlist &=
|
||||
## If disabled, the X-Content-Type-Options header will not be included. Default false.
|
||||
attribute disabled {xsd:boolean}?
|
||||
|
||||
header=
|
||||
## Add additional headers to the response.
|
||||
|
|
|
@ -2273,6 +2273,12 @@
|
|||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="headers-options.attlist">
|
||||
<xs:attribute name="defaults-disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies if the default headers should be disabled. Default false.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies if headers should be disabled. Default false.
|
||||
|
@ -2290,6 +2296,12 @@
|
|||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="hsts-options.attlist">
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies if HTTP Strict Transport Security (HSTS) should be disabled. Default false.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="include-subdomains" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies if subdomains should be included. Default true.
|
||||
|
@ -2317,8 +2329,18 @@
|
|||
every request
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType/>
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:cache-control.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="cache-control.attlist">
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies if Cache Control should be disabled. Default false.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="frame-options">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options
|
||||
|
@ -2330,6 +2352,12 @@
|
|||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="frame-options.attlist">
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If disabled, the X-Frame-Options header will not be included. Default false.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="policy">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specify the policy to use for the X-Frame-Options-Header.
|
||||
|
@ -2387,9 +2415,16 @@
|
|||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="xss-protection.attlist">
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>disable the X-XSS-Protection header. Default is 'false' meaning it is enabled.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="enabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>enable or disable the X-XSS-Protection header. Default is 'true' meaning it is enabled.
|
||||
<xs:documentation>specify that XSS Protection should be explicitly enabled or disabled. Default is 'true'
|
||||
meaning it is enabled.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
@ -2405,8 +2440,18 @@
|
|||
<xs:documentation>Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType/>
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:content-type-options.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="content-type-options.attlist">
|
||||
<xs:attribute name="disabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If disabled, the X-Content-Type-Options header will not be included. Default false.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="header">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Add additional headers to the response.
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
|||
import org.springframework.security.web.access.AccessDeniedHandler
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
|
||||
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.servlet.support.RequestDataValueProcessor;
|
||||
|
@ -77,7 +78,10 @@ class HeadersConfigurerTests extends BaseSpringSpec {
|
|||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().contentTypeOptions()
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.contentTypeOptions()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +99,10 @@ class HeadersConfigurerTests extends BaseSpringSpec {
|
|||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().frameOptions()
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.frameOptions()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +121,10 @@ class HeadersConfigurerTests extends BaseSpringSpec {
|
|||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().httpStrictTransportSecurity()
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.httpStrictTransportSecurity()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +144,10 @@ class HeadersConfigurerTests extends BaseSpringSpec {
|
|||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().cacheControl()
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.cacheControl()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +165,37 @@ class HeadersConfigurerTests extends BaseSpringSpec {
|
|||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().xssProtection()
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.xssProtection()
|
||||
}
|
||||
}
|
||||
|
||||
def "headers custom x-frame-options"() {
|
||||
setup:
|
||||
loadConfig(HeadersCustomSameOriginConfig)
|
||||
request.secure = true
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then:
|
||||
responseHeaders == ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'SAMEORIGIN',
|
||||
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block']
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.headers()
|
||||
.frameOptions().sameOrigin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.springframework.security.web.header.writers.frameoptions.StaticAllowF
|
|||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Tests to verify that all the functionality of <headers> attributes is present
|
||||
|
@ -80,7 +81,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
protected void configure(HttpSecurity http) {
|
||||
http
|
||||
.headers()
|
||||
.addHeaderWriter(new CacheControlHeadersWriter())
|
||||
.defaultsDisabled()
|
||||
.cacheControl()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +102,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
protected void configure(HttpSecurity http) {
|
||||
http
|
||||
.headers()
|
||||
.addHeaderWriter(new HstsHeaderWriter())
|
||||
.defaultsDisabled()
|
||||
.httpStrictTransportSecurity()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,8 +123,11 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
http
|
||||
.headers()
|
||||
// hsts@request-matcher-ref, hsts@max-age-seconds, hsts@include-subdomains
|
||||
// Additional Constructors are provided to leverage default values
|
||||
.addHeaderWriter(new HstsHeaderWriter(AnyRequestMatcher.INSTANCE, 15768000, false))
|
||||
.defaultsDisabled()
|
||||
.httpStrictTransportSecurity()
|
||||
.requestMatcher(AnyRequestMatcher.INSTANCE)
|
||||
.maxAgeInSeconds(15768000)
|
||||
.includeSubDomains(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +147,9 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
http
|
||||
.headers()
|
||||
// frame-options@policy=SAMEORIGIN
|
||||
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN))
|
||||
.defaultsDisabled()
|
||||
.frameOptions()
|
||||
.sameOrigin()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,6 +172,7 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
http
|
||||
.headers()
|
||||
// frame-options@ref
|
||||
.defaultsDisabled()
|
||||
.addHeaderWriter(new XFrameOptionsHeaderWriter(new StaticAllowFromStrategy(new URI("https://example.com"))))
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +193,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
http
|
||||
.headers()
|
||||
// xss-protection
|
||||
.addHeaderWriter(new XXssProtectionHeaderWriter())
|
||||
.defaultsDisabled()
|
||||
.xssProtection()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +214,10 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
http
|
||||
.headers()
|
||||
// xss-protection@enabled and xss-protection@block
|
||||
.addHeaderWriter(new XXssProtectionHeaderWriter(enabled:true,block:false))
|
||||
.defaultsDisabled()
|
||||
.xssProtection()
|
||||
.xssProtectionEnabled(true)
|
||||
.block(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +237,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
http
|
||||
.headers()
|
||||
// content-type-options
|
||||
.addHeaderWriter(new XContentTypeOptionsHeaderWriter())
|
||||
.defaultsDisabled()
|
||||
.contentTypeOptions()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,6 +259,7 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||
protected void configure(HttpSecurity http) {
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,13 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
|||
* @author Rob Winch
|
||||
*/
|
||||
class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
||||
def defaultHeaders = ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block']
|
||||
def 'headers disabled'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
|
@ -62,13 +69,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block'])
|
||||
assertHeaders(response, defaultHeaders)
|
||||
}
|
||||
|
||||
def 'http headers with empty headers'() {
|
||||
|
@ -82,18 +83,33 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['X-Content-Type-Options':'nosniff',
|
||||
'X-Frame-Options':'DENY',
|
||||
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
'Pragma':'no-cache',
|
||||
'X-XSS-Protection' : '1; mode=block'])
|
||||
assertHeaders(response, defaultHeaders)
|
||||
}
|
||||
|
||||
def 'http headers frame-options@policy=SAMEORIGIN with defaults'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(policy:'SAMEORIGIN')
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
|
||||
expect:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
||||
|
||||
// --- defaults disabled
|
||||
|
||||
def 'http headers content-type-options'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'content-type-options'()
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +125,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def 'http headers frame-options defaults to DENY'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'()
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +141,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def 'http headers frame-options DENY'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'(policy : 'DENY')
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +157,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def 'http headers frame-options SAMEORIGIN'() {
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'(policy : 'SAMEORIGIN')
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +174,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers frame-options ALLOW-FROM no origin reports error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'(policy : 'ALLOW-FROM', strategy : 'static')
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +190,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers frame-options ALLOW-FROM spaces only origin reports error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'(policy : 'ALLOW-FROM', strategy: 'static', value : ' ')
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +206,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers frame-options ALLOW-FROM'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'(policy : 'ALLOW-FROM', strategy: 'static', value : 'https://example.com')
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +223,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers frame-options ALLOW-FROM with whitelist strategy'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'frame-options'(policy : 'ALLOW-FROM', strategy: 'whitelist', value : 'https://example.com')
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +243,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers header a=b'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'header'(name : 'a', value: 'b')
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +260,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers header a=b and c=d'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'header'(name : 'a', value: 'b')
|
||||
'header'(name : 'c', value: 'd')
|
||||
}
|
||||
|
@ -262,7 +278,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers with ref'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'header'(ref:'headerWriter')
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +298,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers header no name produces error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'header'(value: 'b')
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +311,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers header no value produces error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'header'(name: 'a')
|
||||
}
|
||||
}
|
||||
|
@ -308,7 +324,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers xss-protection defaults'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'xss-protection'()
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +341,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers xss-protection enabled=true'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'xss-protection'(enabled:'true')
|
||||
}
|
||||
}
|
||||
|
@ -342,7 +358,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers xss-protection enabled=false'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'xss-protection'(enabled:'false')
|
||||
}
|
||||
}
|
||||
|
@ -359,7 +375,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers xss-protection enabled=false and block=true produces error'() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'xss-protection'(enabled:'false', block:'true')
|
||||
}
|
||||
}
|
||||
|
@ -375,7 +391,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers cache-control'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'cache-control'()
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +409,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers hsts'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'hsts'()
|
||||
}
|
||||
}
|
||||
|
@ -409,7 +425,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers hsts default only invokes on HttpServletRequest.isSecure = true'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'hsts'()
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +441,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def 'http headers hsts custom'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'headers'('defaults-disabled':true) {
|
||||
'hsts'('max-age-seconds':'1','include-subdomains':false, 'request-matcher-ref' : 'matcher')
|
||||
}
|
||||
}
|
||||
|
@ -440,6 +456,187 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
assertHeaders(response, ['Strict-Transport-Security': 'max-age=1'])
|
||||
}
|
||||
|
||||
// --- disable single default header ---
|
||||
|
||||
def 'http headers cache-controls@disabled=true'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'cache-control'(disabled:true)
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('Cache-Control')
|
||||
expectedHeaders.remove('Expires')
|
||||
expectedHeaders.remove('Pragma')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
||||
def 'http headers content-type-options@disabled=true'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'content-type-options'(disabled:true)
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('X-Content-Type-Options')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
||||
def 'http headers hsts@disabled=true'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'hsts'(disabled:true)
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('Strict-Transport-Security')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
||||
def 'http headers frame-options@disabled=true'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(disabled:true)
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('X-Frame-Options')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
||||
def 'http headers xss-protection@disabled=true'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'(disabled:true)
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('X-XSS-Protection')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
||||
// --- disable error handling ---
|
||||
|
||||
def 'http headers hsts@disabled=true no include-subdomains'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'hsts'(disabled:true,'include-subdomains':true)
|
||||
}
|
||||
}
|
||||
when:
|
||||
createAppContext()
|
||||
then:
|
||||
BeanDefinitionParsingException expected = thrown()
|
||||
expected.message.contains 'include-subdomains'
|
||||
}
|
||||
|
||||
def 'http headers hsts@disabled=true no max-age'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'hsts'(disabled:true,'max-age-seconds':123)
|
||||
}
|
||||
}
|
||||
when:
|
||||
createAppContext()
|
||||
then:
|
||||
BeanDefinitionParsingException expected = thrown()
|
||||
expected.message.contains 'max-age'
|
||||
}
|
||||
|
||||
def 'http headers hsts@disabled=true no matcher-ref'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'hsts'(disabled:true,'request-matcher-ref':'matcher')
|
||||
}
|
||||
}
|
||||
xml.'b:bean'(id: 'matcher', 'class': AnyRequestMatcher.name)
|
||||
when:
|
||||
createAppContext()
|
||||
then:
|
||||
BeanDefinitionParsingException expected = thrown()
|
||||
expected.message.contains 'request-matcher-ref'
|
||||
}
|
||||
|
||||
def 'http xss@disabled=true no enabled'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'(disabled:true,'enabled':true)
|
||||
}
|
||||
}
|
||||
when:
|
||||
createAppContext()
|
||||
then:
|
||||
BeanDefinitionParsingException expected = thrown()
|
||||
expected.message.contains 'enabled'
|
||||
}
|
||||
|
||||
def 'http xss@disabled=true no block'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'xss-protection'(disabled:true,'block':true)
|
||||
}
|
||||
}
|
||||
when:
|
||||
createAppContext()
|
||||
then:
|
||||
BeanDefinitionParsingException expected = thrown()
|
||||
expected.message.contains 'block'
|
||||
}
|
||||
|
||||
def 'http frame-options@disabled=true no policy'() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'headers'() {
|
||||
'frame-options'(disabled:true,'policy':'DENY')
|
||||
}
|
||||
}
|
||||
when:
|
||||
createAppContext()
|
||||
then:
|
||||
BeanDefinitionParsingException expected = thrown()
|
||||
expected.message.contains 'policy'
|
||||
}
|
||||
|
||||
def assertHeaders(MockHttpServletResponse response, Map<String,String> expected) {
|
||||
assert response.headerNames == expected.keySet()
|
||||
expected.each { headerName, value ->
|
||||
|
|
|
@ -3303,7 +3303,23 @@ You can also specify a custom RequestMatcher to determine which requests are pro
|
|||
This section discusses Spring Security's support for adding various security headers to the response.
|
||||
|
||||
=== Default Security Headers
|
||||
Spring Security allows users to easily inject the default security headers to assist in protecting their application. The following is a list of the current __Default Security Headers__ provided by Spring Security:
|
||||
Spring Security allows users to easily inject the default security headers to assist in protecting their application.
|
||||
The default for Spring Security is to include the following headers:
|
||||
|
||||
[source,http]
|
||||
----
|
||||
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
|
||||
Pragma: no-cache
|
||||
Expires: 0
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
|
||||
X-Frame-Options: DENY
|
||||
X-XSS-Protection: 1; mode=block
|
||||
----
|
||||
|
||||
NOTE: Strict-Transport-Security is only added on HTTPS requests
|
||||
|
||||
For additional details on each of these headers, refer to the corresponding sections:
|
||||
|
||||
* <<headers-cache-control,Cache Control>>
|
||||
* <<headers-content-type-options,Content Type Options>>
|
||||
|
@ -3311,9 +3327,48 @@ Spring Security allows users to easily inject the default security headers to as
|
|||
* <<headers-frame-options,X-Frame-Options>>
|
||||
* <<headers-xss-protection,X-XSS-Protection>>
|
||||
|
||||
While each of these headers are considered best practice, it should be noted that not all clients utilize the headers, so additional testing is encouraged. As of Spring Security 4.0, HTTP Security response headers are enabled by default.
|
||||
While each of these headers are considered best practice, it should be noted that not all clients utilize the headers, so additional testing is encouraged.
|
||||
|
||||
Alternatively, you can choose to explicitly list the headers you wish to include. For example, the following is the same the default configuration. Removing any of the elements will remove that header from the responses.
|
||||
You can customize specific headers.
|
||||
For example, assume that want your HTTP response headers to look like the following:
|
||||
|
||||
[source,http]
|
||||
----
|
||||
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
|
||||
Pragma: no-cache
|
||||
Expires: 0
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
X-XSS-Protection: 1; mode=block
|
||||
----
|
||||
|
||||
Specifically, you want all of the default headers with the following customizations:
|
||||
|
||||
* <<headers-frame-options,X-Frame-Options>> to allow any request from same domain
|
||||
* <<headers-hsts,HTTP Strict Transport Security (HSTS)>> will not be addded to the response
|
||||
|
||||
You can easily do this with the following Java Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig extends
|
||||
WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ...
|
||||
.headers()
|
||||
.frameOptions()
|
||||
.sameOrigin()
|
||||
.and()
|
||||
.hsts().disable();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, if you are using Spring Security XML Configuration, you can use the following:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
@ -3321,27 +3376,50 @@ Alternatively, you can choose to explicitly list the headers you wish to include
|
|||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<cache-control />
|
||||
<content-type-options />
|
||||
<hsts />
|
||||
<frame-options />
|
||||
<xss-protection />
|
||||
<frame-options policy="SAMEORIGIN" />
|
||||
<hsts disable="true"/>
|
||||
</headers>
|
||||
</http>
|
||||
----
|
||||
|
||||
If necessary, you can disable the HTTP Security response headers with the following configuration:
|
||||
If you do not want the defaults to be added and want explicit control over what should be used, you can disable the defaults.
|
||||
An example for both Java and XML based configuration is provided below:
|
||||
|
||||
If you are using Spring Security's Java Configuration the following will only add <<headers-cache-control,Cache Control>>.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig extends
|
||||
WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ...
|
||||
.headers()
|
||||
// do not use any default headers unless explicitly listed
|
||||
.defaultsDisabled()
|
||||
.cacheControl();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following XML will only add <<headers-cache-control,Cache Control>>.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<!-- ... -->
|
||||
|
||||
<headers disabled="true" />
|
||||
<headers defaults-disabled="true">
|
||||
<cache-control/>
|
||||
</headers>
|
||||
</http>
|
||||
----
|
||||
|
||||
If you are using Spring Security's Java configuration, all of the default security headers are added by default. They can be disabled using the Java configuration below:
|
||||
|
||||
If necessary, you can disable all of the HTTP Security response headers with the following Java Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
@ -3358,23 +3436,15 @@ public class WebSecurityConfig extends
|
|||
}
|
||||
----
|
||||
|
||||
As soon as you specify any headers that should be included, then only those headers will be include. For example, the following configuration will include support for <<headers-cache-control,Cache Control>> and <<headers-frame-options,X-Frame-Options>> only.
|
||||
If necessary, you can disable all of the HTTP Security response headers with the following XML configuration below:
|
||||
|
||||
[source,java]
|
||||
[source,xml]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig extends
|
||||
WebSecurityConfigurerAdapter {
|
||||
<http>
|
||||
<!-- ... -->
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ...
|
||||
.headers()
|
||||
.cacheControl()
|
||||
.frameOptions();
|
||||
}
|
||||
}
|
||||
<headers disabled="true" />
|
||||
</http>
|
||||
----
|
||||
|
||||
[[headers-cache-control]]
|
||||
|
@ -3388,14 +3458,15 @@ Pragma: no-cache
|
|||
Expires: 0
|
||||
----
|
||||
|
||||
Simply adding the <<nsa-headers,<headers>>> element with no child elements will automatically add Cache Control and quite a few other protections. However, if you only want cache control, you can enable this feature using Spring Security's XML namespace with the <<nsa-cache-control,<cache-control>>> element.
|
||||
Simply adding the <<nsa-headers,<headers>>> element with no child elements will automatically add Cache Control and quite a few other protections.
|
||||
However, if you only want cache control, you can enable this feature using Spring Security's XML namespace with the <<nsa-cache-control,<cache-control>>> element and the <<nsa-headers-defaults-disabled,headers@defaults-disabled>> attribute.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<headers defaults-disable="true">
|
||||
<cache-control />
|
||||
</headers>
|
||||
</http>
|
||||
|
@ -3414,6 +3485,7 @@ public class WebSecurityConfig extends
|
|||
http
|
||||
// ...
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.cacheControl();
|
||||
}
|
||||
}
|
||||
|
@ -3458,14 +3530,15 @@ Content sniffing can be disabled by adding the following header to our response:
|
|||
X-Content-Type-Options: nosniff
|
||||
----
|
||||
|
||||
Just as with the cache control element, the nosniff directive is added by default when using the <headers> element with no child elements. However, if you want more control over which headers are added you can use the <<nsa-content-type-options,<content-type-options>>> element as shown below:
|
||||
Just as with the cache control element, the nosniff directive is added by default when using the <headers> element with no child elements.
|
||||
However, if you want more control over which headers are added you can use the <<nsa-content-type-options,<content-type-options>>> element and the <<nsa-headers-defaults-disabled,headers@defaults-disabled>> attribute as shown below:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<headers defaults-disabled="true">
|
||||
<content-type-options />
|
||||
</headers>
|
||||
</http>
|
||||
|
@ -3484,6 +3557,7 @@ public class WebSecurityConfig extends
|
|||
http
|
||||
// ...
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.contentTypeOptions();
|
||||
}
|
||||
}
|
||||
|
@ -3509,7 +3583,7 @@ Strict-Transport-Security: max-age=31536000 ; includeSubDomains
|
|||
|
||||
The optional includeSubDomains directive instructs Spring Security that subdomains (i.e. secure.mybank.example.com) should also be treated as an HSTS domain.
|
||||
|
||||
As with the other headers, Spring Security adds the previous header to the response when the <headers> element is specified with no child elements. It is also automatically added when you are using Java Configuration. You can also only use HSTS headers with the <<nsa-hsts,<hsts>>> element as shown below:
|
||||
As with the other headers, Spring Security adds HSTS by default. You can customize HSTS headers with the <<nsa-hsts,<hsts>>> element as shown below:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
@ -3517,7 +3591,9 @@ As with the other headers, Spring Security adds the previous header to the respo
|
|||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<hsts />
|
||||
<hsts
|
||||
include-subdomains="true"
|
||||
max-age-seconds="31536000" />
|
||||
</headers>
|
||||
</http>
|
||||
----
|
||||
|
@ -3535,7 +3611,9 @@ public class WebSecurityConfig extends
|
|||
http
|
||||
// ...
|
||||
.headers()
|
||||
.httpStrictTransportSecurity();
|
||||
.httpStrictTransportSecurity()
|
||||
.includeSubdomains(true)
|
||||
.maxAgeSeconds(31536000);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
@ -3558,7 +3636,11 @@ A more modern approach to address clickjacking is to use https://developer.mozil
|
|||
X-Frame-Options: DENY
|
||||
----
|
||||
|
||||
The X-Frame-Options response header instructs the browser to prevent any site with this header in the response from being rendered within a frame. As with the other response headers, this is automatically included when the <headers> element is specified with no child elements. You can also explicitly specify the <<nsa-frame-options,frame-options>> element to control which headers are added to the response.
|
||||
The X-Frame-Options response header instructs the browser to prevent any site with this header in the response from being rendered within a frame.
|
||||
By default, Spring Security disables rendering within an iframe.
|
||||
|
||||
You can customize X-Frame-Options with the <<nsa-frame-options,frame-options>> element.
|
||||
For example, the following will instruct Spring Security to use "X-Frame-Options: SAMEORIGIN" which allows iframes within the same domain:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
@ -3566,12 +3648,13 @@ The X-Frame-Options response header instructs the browser to prevent any site wi
|
|||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<frame-options />
|
||||
<frame-options
|
||||
policy="SAMEORIGIN" />
|
||||
</headers>
|
||||
</http>
|
||||
----
|
||||
|
||||
Similarly, you can enable only frame options within Java Configuration with the following:
|
||||
Similarly, you can customize frame options to use the same origin within Java Configuration using the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
@ -3584,13 +3667,12 @@ public class WebSecurityConfig extends
|
|||
http
|
||||
// ...
|
||||
.headers()
|
||||
.frameOptions();
|
||||
.frameOptions()
|
||||
.sameOrigin();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If you want to change the value for the X-Frame-Options header, then you can use a <<headers-headers-writer,XFrameOptionsHeaderWriter instance>>.
|
||||
|
||||
[[headers-xss-protection]]
|
||||
==== X-XSS-Protection
|
||||
Some browsers have built in support for filtering out https://www.owasp.org/index.php/Testing_for_Reflected_Cross_site_scripting_(OWASP-DV-001)[reflected XSS attacks]. This is by no means full proof, but does assist in XSS protection.
|
||||
|
@ -3602,7 +3684,7 @@ The filtering is typically enabled by default, so adding the header typically ju
|
|||
X-XSS-Protection: 1; mode=block
|
||||
----
|
||||
|
||||
This header is included by default when the <headers> element is specified with no child elements. We can explicitly state it using the <<nsa-xss-protection,xss-protection>> element as shown below:
|
||||
This header is included by default. However, we can customize it if we wanted. For example:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
@ -3610,12 +3692,12 @@ This header is included by default when the <headers> element is specified with
|
|||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<xss-protection />
|
||||
<xss-protection block="false"/>
|
||||
</headers>
|
||||
</http>
|
||||
----
|
||||
|
||||
Similarly, you can enable only xss protection within Java Configuration with the following:
|
||||
Similarly, you can customize xss protection within Java Configuration with the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
@ -3628,7 +3710,8 @@ public class WebSecurityConfig extends
|
|||
http
|
||||
// ...
|
||||
.headers()
|
||||
.xssProtection();
|
||||
.xssProtection()
|
||||
.block(false);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
@ -3639,7 +3722,11 @@ Spring Security has mechanisms to make it convenient to add the more common secu
|
|||
|
||||
[[headers-static]]
|
||||
==== Static Headers
|
||||
There may be times you wish to inject custom security headers into your application that are not supported out of the box. For example, perhaps you wish to have early support for http://www.w3.org/TR/CSP/[Content Security Policy] in order to ensure that resources are only loaded from the same origin. Since support for Content Security Policy has not been finalized, browsers use one of two common extension headers to implement the feature. This means we will need to inject the policy twice. An example of the headers can be seen below:
|
||||
There may be times you wish to inject custom security headers into your application that are not supported out of the box.
|
||||
For example, perhaps you wish to have early support for http://www.w3.org/TR/CSP/[Content Security Policy] in order to ensure that resources are only loaded from the same origin.
|
||||
Since support for Content Security Policy has not been finalized, browsers use one of two common extension headers to implement the feature.
|
||||
This means we will need to inject the policy twice.
|
||||
An example of the headers can be seen below:
|
||||
|
||||
[source]
|
||||
----
|
||||
|
@ -3736,6 +3823,7 @@ At times you may want to only write a header for certain requests. For example,
|
|||
<!-- ... -->
|
||||
|
||||
<headers>
|
||||
<frame-options disabled="true"/>
|
||||
<header ref="headerWriter"/>
|
||||
</headers>
|
||||
</http>
|
||||
|
@ -3771,6 +3859,7 @@ public class WebSecurityConfig extends
|
|||
http
|
||||
// ...
|
||||
.headers()
|
||||
.frameOptions().disabled()
|
||||
.addHeaderWriter(headerWriter);
|
||||
}
|
||||
}
|
||||
|
@ -6605,6 +6694,10 @@ This element allows for configuring additional (security) headers to be send wit
|
|||
The attributes on the `<headers>` element control the headers element.
|
||||
|
||||
|
||||
[[nsa-headers-defaults-disabled]]
|
||||
* **defaults-disabled**
|
||||
Optional attribute that specifies to disable the default Spring Security's HTTP response headers. The default is false (the default headers are included).
|
||||
|
||||
[[nsa-headers-disabled]]
|
||||
* **disabled**
|
||||
Optional attribute that specifies to disable Spring Security's HTTP response headers. The default is false (the headers are enabled).
|
||||
|
@ -6635,6 +6728,14 @@ Optional attribute that specifies to disable Spring Security's HTTP response hea
|
|||
Adds `Cache-Control`, `Pragma`, and `Expires` headers to ensure that the browser does not cache your secured pages.
|
||||
|
||||
|
||||
[[nsa-cache-control-attributes]]
|
||||
===== <cache-control> Attributes
|
||||
|
||||
[[nsa-cache-control-disabled]]
|
||||
* **disabled**
|
||||
Specifies if Cache Control should be disabled. Default false.
|
||||
|
||||
|
||||
[[nsa-cache-control-parents]]
|
||||
===== Parent Elements of <cache-control>
|
||||
|
||||
|
@ -6651,6 +6752,9 @@ When enabled adds the http://tools.ietf.org/html/rfc6797[Strict-Transport-Securi
|
|||
[[nsa-hsts-attributes]]
|
||||
===== <hsts> Attributes
|
||||
|
||||
[[nsa-hsts-disabled]]
|
||||
* **disabled**
|
||||
Specifies if Strict-Transport-Security should be disabled. Default false.
|
||||
|
||||
[[nsa-hsts-include-subdomains]]
|
||||
* **include-sub-domains**
|
||||
|
@ -6682,6 +6786,9 @@ When enabled adds the http://tools.ietf.org/html/draft-ietf-websec-x-frame-optio
|
|||
[[nsa-frame-options-attributes]]
|
||||
===== <frame-options> Attributes
|
||||
|
||||
[[nsa-frame-options-disabled]]
|
||||
* **disabled**
|
||||
If disabled, the X-Frame-Options header will not be included. Default false.
|
||||
|
||||
[[nsa-frame-options-policy]]
|
||||
* **policy**
|
||||
|
@ -6735,9 +6842,14 @@ Adds the http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-
|
|||
===== <xss-protection> Attributes
|
||||
|
||||
|
||||
[[nsa-xss-protection-disabled]]
|
||||
* **xss-protection-disabled**
|
||||
Do not include the header for http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent[reflected / Type-1 Cross-Site Scripting (XSS)] protection.
|
||||
|
||||
|
||||
[[nsa-xss-protection-enabled]]
|
||||
* **xss-protection-enabled**
|
||||
Enable or Disable http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent[reflected / Type-1 Cross-Site Scripting (XSS)] protection.
|
||||
Explicitly enable or eisable http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent[reflected / Type-1 Cross-Site Scripting (XSS)] protection.
|
||||
|
||||
|
||||
[[nsa-xss-protection-block]]
|
||||
|
@ -6757,6 +6869,13 @@ When true and xss-protection-enabled is true, adds mode=block to the header. Thi
|
|||
Add the X-Content-Type-Options header with the value of nosniff to the response. This http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx[disables MIME-sniffing] for IE8+ and Chrome extensions.
|
||||
|
||||
|
||||
[[nsa-content-type-options-attributes]]
|
||||
===== <content-type-options> Attributes
|
||||
|
||||
[[nsa-content-type-options-disabled]]
|
||||
* **disabled**
|
||||
Specifies if Content Type Options should be disabled. Default false.
|
||||
|
||||
[[nsa-content-type-options-parents]]
|
||||
===== Parent Elements of <content-type-options>
|
||||
|
||||
|
|
Loading…
Reference in New Issue