Allow configuration of headers through nested builder
Issue: gh-5557
This commit is contained in:
parent
6986cf3ef3
commit
758397f102
|
@ -338,6 +338,103 @@ public final class HttpSecurity extends
|
|||
return getOrApply(new HeadersConfigurer<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Security headers to the response. This is activated by default when using
|
||||
* {@link WebSecurityConfigurerAdapter}'s default constructor.
|
||||
*
|
||||
* <h2>Example Configurations</h2>
|
||||
*
|
||||
* Accepting the default provided by {@link WebSecurityConfigurerAdapter} or only invoking
|
||||
* {@link #headers()} without invoking additional methods on it, is the equivalent of:
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .headers(headers ->
|
||||
* headers
|
||||
* .contentTypeOptions(withDefaults())
|
||||
* .xssProtection(withDefaults())
|
||||
* .cacheControl(withDefaults())
|
||||
* .httpStrictTransportSecurity(withDefaults())
|
||||
* .frameOptions(withDefaults()
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* </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(headers -> headers.disable());
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* You can enable only a few of the headers by first invoking
|
||||
* {@link HeadersConfigurer#defaultsDisabled()}
|
||||
* and then invoking the appropriate methods on the {@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(headers ->
|
||||
* headers
|
||||
* .defaultsDisabled()
|
||||
* .cacheControl(withDefaults())
|
||||
* .frameOptions(withDefaults())
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* You can also choose to keep the defaults but explicitly disable a subset of headers.
|
||||
* For example, the following will enable all the default headers except
|
||||
* {@link HeadersConfigurer#frameOptions()}.
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .headers(headers ->
|
||||
* headers
|
||||
* .frameOptions(frameOptions -> frameOptions.disable())
|
||||
* );
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param headersCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link HeadersConfigurer}
|
||||
* @return the {@link HttpSecurity} for further customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HttpSecurity headers(Customizer<HeadersConfigurer<HttpSecurity>> headersCustomizer) throws Exception {
|
||||
headersCustomizer.customize(getOrApply(new HeadersConfigurer<>()));
|
||||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link CorsFilter} to be used. If a bean by the name of corsFilter is
|
||||
* provided, that {@link CorsFilter} is used. Else if corsConfigurationSource is
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -23,6 +23,7 @@ import java.util.Map;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.config.Customizer;
|
||||
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;
|
||||
|
@ -30,6 +31,8 @@ import org.springframework.security.web.header.HeaderWriter;
|
|||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
import org.springframework.security.web.header.writers.*;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
|
||||
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;
|
||||
|
@ -121,6 +124,26 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return contentTypeOptions.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link XContentTypeOptionsHeaderWriter} which inserts the <a href=
|
||||
* "https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx"
|
||||
* >X-Content-Type-Options</a>:
|
||||
*
|
||||
* <pre>
|
||||
* X-Content-Type-Options: nosniff
|
||||
* </pre>
|
||||
*
|
||||
* @param contentTypeOptionsCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link ContentTypeOptionsConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> contentTypeOptions(Customizer<ContentTypeOptionsConfig> contentTypeOptionsCustomizer)
|
||||
throws Exception {
|
||||
contentTypeOptionsCustomizer.customize(contentTypeOptions.enable());
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class ContentTypeOptionsConfig {
|
||||
private XContentTypeOptionsHeaderWriter writer;
|
||||
|
||||
|
@ -174,6 +197,25 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return xssProtection.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* <strong>Note this is not comprehensive XSS protection!</strong>
|
||||
*
|
||||
* <p>
|
||||
* Allows customizing the {@link XXssProtectionHeaderWriter} which adds the <a href=
|
||||
* "https://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>
|
||||
* </p>
|
||||
*
|
||||
* @param xssCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link XXssConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> xssProtection(Customizer<XXssConfig> xssCustomizer) throws Exception {
|
||||
xssCustomizer.customize(xssProtection.enable());
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class XXssConfig {
|
||||
private XXssProtectionHeaderWriter writer;
|
||||
|
||||
|
@ -268,6 +310,26 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return cacheControl.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* <li>Pragma: no-cache</li>
|
||||
* <li>Expires: 0</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param cacheControlCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link CacheControlConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> cacheControl(Customizer<CacheControlConfig> cacheControlCustomizer) throws Exception {
|
||||
cacheControlCustomizer.customize(cacheControl.enable());
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
|
||||
public final class CacheControlConfig {
|
||||
private CacheControlHeadersWriter writer;
|
||||
|
||||
|
@ -319,6 +381,21 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return hsts.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the {@link HstsHeaderWriter} which provides support for <a
|
||||
* href="https://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security
|
||||
* (HSTS)</a>.
|
||||
*
|
||||
* @param hstsCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link HstsConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> httpStrictTransportSecurity(Customizer<HstsConfig> hstsCustomizer) throws Exception {
|
||||
hstsCustomizer.customize(hsts.enable());
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class HstsConfig {
|
||||
private HstsHeaderWriter writer;
|
||||
|
||||
|
@ -440,6 +517,19 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return frameOptions.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the {@link XFrameOptionsHeaderWriter}.
|
||||
*
|
||||
* @param frameOptionsCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link FrameOptionsConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> frameOptions(Customizer<FrameOptionsConfig> frameOptionsCustomizer) throws Exception {
|
||||
frameOptionsCustomizer.customize(frameOptions.enable());
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class FrameOptionsConfig {
|
||||
private XFrameOptionsHeaderWriter writer;
|
||||
|
||||
|
@ -516,6 +606,20 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return hpkp.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the {@link HpkpHeaderWriter} which provides support for <a
|
||||
* href="https://tools.ietf.org/html/rfc7469">HTTP Public Key Pinning (HPKP)</a>.
|
||||
*
|
||||
* @param hpkpCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link HpkpConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> httpPublicKeyPinning(Customizer<HpkpConfig> hpkpCustomizer) throws Exception {
|
||||
hpkpCustomizer.customize(hpkp.enable());
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class HpkpConfig {
|
||||
private HpkpHeaderWriter writer;
|
||||
|
||||
|
@ -713,12 +817,57 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return contentSecurityPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Allows configuration for <a href="https://www.w3.org/TR/CSP2/">Content Security Policy (CSP) Level 2</a>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Calling this method automatically enables (includes) the Content-Security-Policy header in the response
|
||||
* using the supplied security policy directive(s).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Configuration is provided to the {@link ContentSecurityPolicyHeaderWriter} which supports the writing
|
||||
* of the two headers as detailed in the W3C Candidate Recommendation:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Content-Security-Policy</li>
|
||||
* <li>Content-Security-Policy-Report-Only</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see ContentSecurityPolicyHeaderWriter
|
||||
* @param contentSecurityCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link ContentSecurityPolicyConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> contentSecurityPolicy(Customizer<ContentSecurityPolicyConfig> contentSecurityCustomizer)
|
||||
throws Exception {
|
||||
this.contentSecurityPolicy.writer = new ContentSecurityPolicyHeaderWriter();
|
||||
contentSecurityCustomizer.customize(this.contentSecurityPolicy);
|
||||
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class ContentSecurityPolicyConfig {
|
||||
private ContentSecurityPolicyHeaderWriter writer;
|
||||
|
||||
private ContentSecurityPolicyConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the security policy directive(s) to be used in the response header.
|
||||
*
|
||||
* @param policyDirectives the security policy directive(s)
|
||||
* @return the {@link ContentSecurityPolicyConfig} for additional configuration
|
||||
* @throws IllegalArgumentException if policyDirectives is null or empty
|
||||
*/
|
||||
public ContentSecurityPolicyConfig policyDirectives(String policyDirectives) {
|
||||
this.writer.setPolicyDirectives(policyDirectives);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables (includes) the Content-Security-Policy-Report-Only header in the response.
|
||||
*
|
||||
|
@ -860,6 +1009,31 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
return this.referrerPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Allows configuration for <a href="https://www.w3.org/TR/referrer-policy/">Referrer Policy</a>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Configuration is provided to the {@link ReferrerPolicyHeaderWriter} which support the writing
|
||||
* of the header as detailed in the W3C Technical Report:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Referrer-Policy</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see ReferrerPolicyHeaderWriter
|
||||
* @param referrerPolicyCustomizer the {@link Customizer} to provide more options for
|
||||
* the {@link ReferrerPolicyConfig}
|
||||
* @return the {@link HeadersConfigurer} for additional customizations
|
||||
* @throws Exception
|
||||
*/
|
||||
public HeadersConfigurer<H> referrerPolicy(Customizer<ReferrerPolicyConfig> referrerPolicyCustomizer) throws Exception {
|
||||
this.referrerPolicy.writer = new ReferrerPolicyHeaderWriter();
|
||||
referrerPolicyCustomizer.customize(this.referrerPolicy);
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
||||
public final class ReferrerPolicyConfig {
|
||||
|
||||
private ReferrerPolicyHeaderWriter writer;
|
||||
|
@ -867,6 +1041,18 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||
private ReferrerPolicyConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the policy to be used in the response header.
|
||||
*
|
||||
* @param policy a referrer policy
|
||||
* @return the {@link ReferrerPolicyConfig} for additional configuration
|
||||
* @throws IllegalArgumentException if policy is null
|
||||
*/
|
||||
public ReferrerPolicyConfig policy(ReferrerPolicy policy) {
|
||||
this.writer.setPolicy(policy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HeadersConfigurer<H> and() {
|
||||
return HeadersConfigurer.this;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.Map;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
|
@ -87,6 +88,36 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeadersConfiguredInLambdaThenDefaultHeadersInResponse() throws Exception {
|
||||
this.spring.register(HeadersInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff"))
|
||||
.andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name()))
|
||||
.andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains"))
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"))
|
||||
.andExpect(header().string(HttpHeaders.EXPIRES, "0"))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, "no-cache"))
|
||||
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(
|
||||
HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY,
|
||||
HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class HeadersInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeaderDefaultsDisabledAndContentTypeConfiguredThenOnlyContentTypeHeaderInResponse()
|
||||
throws Exception {
|
||||
|
@ -112,6 +143,33 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenOnlyContentTypeConfiguredInLambdaThenOnlyContentTypeHeaderInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(ContentTypeOptionsInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/"))
|
||||
.andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_CONTENT_TYPE_OPTIONS);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ContentTypeOptionsInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.contentTypeOptions(withDefaults())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeaderDefaultsDisabledAndFrameOptionsConfiguredThenOnlyFrameOptionsHeaderInResponse()
|
||||
throws Exception {
|
||||
|
@ -190,6 +248,36 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenOnlyCacheControlConfiguredInLambdaThenCacheControlAndExpiresAndPragmaHeadersInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(CacheControlInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"))
|
||||
.andExpect(header().string(HttpHeaders.EXPIRES, "0"))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, "no-cache"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(HttpHeaders.CACHE_CONTROL,
|
||||
HttpHeaders.EXPIRES, HttpHeaders.PRAGMA);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class CacheControlInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.cacheControl(withDefaults())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssProtectionHeaderInResponse()
|
||||
throws Exception {
|
||||
|
@ -215,6 +303,33 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(XssProtectionInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.xssProtection(withDefaults())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception {
|
||||
this.spring.register(HeadersCustomSameOriginConfig.class).autowire();
|
||||
|
@ -237,6 +352,31 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenFrameOptionsSameOriginConfiguredInLambdaThenFrameOptionsHeaderHasValueSameOrigin()
|
||||
throws Exception {
|
||||
this.spring.register(HeadersCustomSameOriginInLambdaConfig.class).autowire();
|
||||
|
||||
this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name()))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class HeadersCustomSameOriginInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.frameOptions(frameOptionsConfig -> frameOptionsConfig.sameOrigin())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeaderDefaultsDisabledAndPublicHpkpWithNoPinThenNoHeadersInResponse() throws Exception {
|
||||
this.spring.register(HpkpConfigNoPins.class).autowire();
|
||||
|
@ -465,6 +605,38 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHpkpWithReportUriInLambdaThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(HpkpWithReportUriInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY,
|
||||
"max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class HpkpWithReportUriInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.httpPublicKeyPinning(hpkp ->
|
||||
hpkp
|
||||
.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
|
||||
.reportUri("https://example.net/pkp-report")
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenContentSecurityPolicyConfiguredThenContentSecurityPolicyHeaderInResponse() throws Exception {
|
||||
this.spring.register(ContentSecurityPolicyDefaultConfig.class).autowire();
|
||||
|
@ -515,6 +687,38 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenContentSecurityPolicyWithReportOnlyInLambdaThenContentSecurityPolicyReportOnlyHeaderInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(ContentSecurityPolicyReportOnlyInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY,
|
||||
"default-src 'self'; script-src trustedscripts.example.com"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ContentSecurityPolicyReportOnlyInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.contentSecurityPolicy(csp ->
|
||||
csp
|
||||
.policyDirectives("default-src 'self'; script-src trustedscripts.example.com")
|
||||
.reportOnly()
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenContentSecurityPolicyEmptyThenException() {
|
||||
assertThatThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidConfig.class).autowire())
|
||||
|
@ -536,6 +740,58 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenContentSecurityPolicyEmptyInLambdaThenException() {
|
||||
assertThatThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidInLambdaConfig.class).autowire())
|
||||
.isInstanceOf(BeanCreationException.class)
|
||||
.hasRootCauseInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ContentSecurityPolicyInvalidInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.contentSecurityPolicy(csp ->
|
||||
csp.policyDirectives("")
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenContentSecurityPolicyNoPolicyDirectivesInLambdaThenDefaultHeaderValue() throws Exception {
|
||||
this.spring.register(ContentSecurityPolicyNoDirectivesInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_SECURITY_POLICY,
|
||||
"default-src 'self'"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ContentSecurityPolicyNoDirectivesInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.contentSecurityPolicy(withDefaults())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenReferrerPolicyConfiguredThenReferrerPolicyHeaderInResponse() throws Exception {
|
||||
this.spring.register(ReferrerPolicyDefaultConfig.class).autowire();
|
||||
|
@ -560,6 +816,32 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenReferrerPolicyInLambdaThenReferrerPolicyHeaderInResponse() throws Exception {
|
||||
this.spring.register(ReferrerPolicyDefaultInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string("Referrer-Policy", ReferrerPolicy.NO_REFERRER.getPolicy()))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy");
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ReferrerPolicyDefaultInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.referrerPolicy()
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenReferrerPolicyConfiguredWithCustomValueThenReferrerPolicyHeaderWithCustomValueInResponse()
|
||||
throws Exception {
|
||||
|
@ -585,6 +867,34 @@ public class HeadersConfigurerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenReferrerPolicyConfiguredWithCustomValueInLambdaThenCustomValueInResponse() throws Exception {
|
||||
this.spring.register(ReferrerPolicyCustomInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string("Referrer-Policy", ReferrerPolicy.SAME_ORIGIN.getPolicy()))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy");
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ReferrerPolicyCustomInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.referrerPolicy(referrerPolicy ->
|
||||
referrerPolicy.policy(ReferrerPolicy.SAME_ORIGIN)
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenFeaturePolicyConfiguredThenFeaturePolicyHeaderInResponse() throws Exception {
|
||||
this.spring.register(FeaturePolicyConfig.class).autowire();
|
||||
|
@ -656,4 +966,32 @@ public class HeadersConfigurerTests {
|
|||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHstsConfiguredWithPreloadInLambdaThenStrictTransportSecurityHeaderWithPreloadInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(HstsWithPreloadInLambdaConfig.class).autowire();
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY,
|
||||
"max-age=31536000 ; includeSubDomains ; preload"))
|
||||
.andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class HstsWithPreloadInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.httpStrictTransportSecurity(hstsConfig -> hstsConfig.preload(true))
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,10 +81,20 @@ public final class ContentSecurityPolicyHeaderWriter implements HeaderWriter {
|
|||
|
||||
private static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER = "Content-Security-Policy-Report-Only";
|
||||
|
||||
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
|
||||
|
||||
private String policyDirectives;
|
||||
|
||||
private boolean reportOnly;
|
||||
|
||||
/**
|
||||
* Creates a new instance. Default value: default-src 'self'
|
||||
*/
|
||||
public ContentSecurityPolicyHeaderWriter() {
|
||||
setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
|
||||
this.reportOnly = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
|
|
|
@ -43,6 +43,15 @@ public class ContentSecurityPolicyHeaderWriterTests {
|
|||
writer = new ContentSecurityPolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHeadersWhenNoPolicyDirectivesThenUsesDefault() {
|
||||
ContentSecurityPolicyHeaderWriter noPolicyWriter = new ContentSecurityPolicyHeaderWriter();
|
||||
noPolicyWriter.writeHeaders(request, response);
|
||||
|
||||
assertThat(response.getHeaderNames()).hasSize(1);
|
||||
assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHeadersContentSecurityPolicyDefault() {
|
||||
writer.writeHeaders(request, response);
|
||||
|
@ -64,6 +73,16 @@ public class ContentSecurityPolicyHeaderWriterTests {
|
|||
assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(policyDirectives);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHeadersWhenNoPolicyDirectivesReportOnlyThenUsesDefault() {
|
||||
ContentSecurityPolicyHeaderWriter noPolicyWriter = new ContentSecurityPolicyHeaderWriter();
|
||||
writer.setReportOnly(true);
|
||||
noPolicyWriter.writeHeaders(request, response);
|
||||
|
||||
assertThat(response.getHeaderNames()).hasSize(1);
|
||||
assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHeadersContentSecurityPolicyReportOnlyDefault() {
|
||||
writer.setReportOnly(true);
|
||||
|
|
Loading…
Reference in New Issue