diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
index bad4a7fbb4..75e818caf9 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
@@ -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.
+ *
+ *
Example Configurations
+ *
+ * Accepting the default provided by {@link WebSecurityConfigurerAdapter} or only invoking
+ * {@link #headers()} without invoking additional methods on it, is the equivalent of:
+ *
+ *
+ * @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()
+ * );
+ * }
+ * }
+ *
+ *
+ * You can disable the headers using the following:
+ *
+ *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+ *
+ * @Override
+ * protected void configure(HttpSecurity http) throws Exception {
+ * http
+ * .headers(headers -> headers.disable());
+ * }
+ * }
+ *
+ *
+ * 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.
+ *
+ *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+ *
+ * @Override
+ * protected void configure(HttpSecurity http) throws Exception {
+ * http
+ * .headers(headers ->
+ * headers
+ * .defaultsDisabled()
+ * .cacheControl(withDefaults())
+ * .frameOptions(withDefaults())
+ * );
+ * }
+ * }
+ *
+ *
+ * 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()}.
+ *
+ *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+ *
+ * @Override
+ * protected void configure(HttpSecurity http) throws Exception {
+ * http
+ * .headers(headers ->
+ * headers
+ * .frameOptions(frameOptions -> frameOptions.disable())
+ * );
+ * }
+ *
+ *
+ * @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> 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
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java
index 1f38253d40..77c840d5a0 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java
@@ -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> extends
return contentTypeOptions.enable();
}
+ /**
+ * Configures the {@link XContentTypeOptionsHeaderWriter} which inserts the X-Content-Type-Options:
+ *
+ *
+ * X-Content-Type-Options: nosniff
+ *
+ *
+ * @param contentTypeOptionsCustomizer the {@link Customizer} to provide more options for
+ * the {@link ContentTypeOptionsConfig}
+ * @return the {@link HeadersConfigurer} for additional customizations
+ * @throws Exception
+ */
+ public HeadersConfigurer contentTypeOptions(Customizer contentTypeOptionsCustomizer)
+ throws Exception {
+ contentTypeOptionsCustomizer.customize(contentTypeOptions.enable());
+ return HeadersConfigurer.this;
+ }
+
public final class ContentTypeOptionsConfig {
private XContentTypeOptionsHeaderWriter writer;
@@ -174,6 +197,25 @@ public class HeadersConfigurer> extends
return xssProtection.enable();
}
+ /**
+ * Note this is not comprehensive XSS protection!
+ *
+ *
+ * Allows customizing the {@link XXssProtectionHeaderWriter} which adds the X-XSS-Protection header
+ *
+ *
+ * @param xssCustomizer the {@link Customizer} to provide more options for
+ * the {@link XXssConfig}
+ * @return the {@link HeadersConfigurer} for additional customizations
+ * @throws Exception
+ */
+ public HeadersConfigurer xssProtection(Customizer xssCustomizer) throws Exception {
+ xssCustomizer.customize(xssProtection.enable());
+ return HeadersConfigurer.this;
+ }
+
public final class XXssConfig {
private XXssProtectionHeaderWriter writer;
@@ -268,6 +310,26 @@ public class HeadersConfigurer> extends
return cacheControl.enable();
}
+ /**
+ * Allows customizing the {@link CacheControlHeadersWriter}. Specifically it adds the
+ * following headers:
+ *
+ * - Cache-Control: no-cache, no-store, max-age=0, must-revalidate
+ * - Pragma: no-cache
+ * - Expires: 0
+ *
+ *
+ * @param cacheControlCustomizer the {@link Customizer} to provide more options for
+ * the {@link CacheControlConfig}
+ * @return the {@link HeadersConfigurer} for additional customizations
+ * @throws Exception
+ */
+ public HeadersConfigurer cacheControl(Customizer cacheControlCustomizer) throws Exception {
+ cacheControlCustomizer.customize(cacheControl.enable());
+ return HeadersConfigurer.this;
+ }
+
+
public final class CacheControlConfig {
private CacheControlHeadersWriter writer;
@@ -319,6 +381,21 @@ public class HeadersConfigurer> extends
return hsts.enable();
}
+ /**
+ * Allows customizing the {@link HstsHeaderWriter} which provides support for HTTP Strict Transport Security
+ * (HSTS).
+ *
+ * @param hstsCustomizer the {@link Customizer} to provide more options for
+ * the {@link HstsConfig}
+ * @return the {@link HeadersConfigurer} for additional customizations
+ * @throws Exception
+ */
+ public HeadersConfigurer httpStrictTransportSecurity(Customizer hstsCustomizer) throws Exception {
+ hstsCustomizer.customize(hsts.enable());
+ return HeadersConfigurer.this;
+ }
+
public final class HstsConfig {
private HstsHeaderWriter writer;
@@ -440,6 +517,19 @@ public class HeadersConfigurer> 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 frameOptions(Customizer frameOptionsCustomizer) throws Exception {
+ frameOptionsCustomizer.customize(frameOptions.enable());
+ return HeadersConfigurer.this;
+ }
+
public final class FrameOptionsConfig {
private XFrameOptionsHeaderWriter writer;
@@ -516,6 +606,20 @@ public class HeadersConfigurer> extends
return hpkp.enable();
}
+ /**
+ * Allows customizing the {@link HpkpHeaderWriter} which provides support for HTTP Public Key Pinning (HPKP).
+ *
+ * @param hpkpCustomizer the {@link Customizer} to provide more options for
+ * the {@link HpkpConfig}
+ * @return the {@link HeadersConfigurer} for additional customizations
+ * @throws Exception
+ */
+ public HeadersConfigurer httpPublicKeyPinning(Customizer hpkpCustomizer) throws Exception {
+ hpkpCustomizer.customize(hpkp.enable());
+ return HeadersConfigurer.this;
+ }
+
public final class HpkpConfig {
private HpkpHeaderWriter writer;
@@ -713,12 +817,57 @@ public class HeadersConfigurer> extends
return contentSecurityPolicy;
}
+ /**
+ *
+ * Allows configuration for Content Security Policy (CSP) Level 2.
+ *
+ *
+ *
+ * Calling this method automatically enables (includes) the Content-Security-Policy header in the response
+ * using the supplied security policy directive(s).
+ *
+ *
+ *
+ * Configuration is provided to the {@link ContentSecurityPolicyHeaderWriter} which supports the writing
+ * of the two headers as detailed in the W3C Candidate Recommendation:
+ *
+ *
+ * - Content-Security-Policy
+ * - Content-Security-Policy-Report-Only
+ *
+ *
+ * @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 contentSecurityPolicy(Customizer 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> extends
return this.referrerPolicy;
}
+ /**
+ *
+ * Allows configuration for Referrer Policy.
+ *
+ *
+ *
+ * Configuration is provided to the {@link ReferrerPolicyHeaderWriter} which support the writing
+ * of the header as detailed in the W3C Technical Report:
+ *
+ *
+ *
+ * @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 referrerPolicy(Customizer 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> 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 and() {
return HeadersConfigurer.this;
}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java
index f8ed9adcf2..0ca822a6fd 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java
@@ -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
+ }
+ }
}
diff --git a/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java
index 12ad08e978..4ae86198f3 100644
--- a/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java
+++ b/web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java
@@ -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
*
diff --git a/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java
index 5ff062f434..2f3c7914e8 100644
--- a/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java
+++ b/web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java
@@ -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);