Browse Source

Migrate HeadersConfigurerTests groovy->java

Issue: gh-4939
Eleftheria Stein 6 years ago
parent
commit
16b0d782f4

+ 0 - 568
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy

@@ -1,568 +0,0 @@
-/*
- * Copyright 2002-2018 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.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.config.annotation.web.configurers
-
-import org.springframework.beans.factory.BeanCreationException
-import org.springframework.security.config.annotation.BaseSpringSpec
-import org.springframework.security.config.annotation.web.builders.HttpSecurity
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
-
-import static org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy
-
-/**
- * Tests for {@link HeadersConfigurer}.
- *
- * @author Rob Winch
- * @author Tim Ysewyn
- * @author Joe Grandja
- * @author Eddú Meléndez
- * @author Vedran Pavic
- */
-class HeadersConfigurerTests extends BaseSpringSpec {
-
-	def "headers"() {
-		setup:
-			loadConfig(HeadersConfig)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['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']
-	}
-
-	@EnableWebSecurity
-	static class HeadersConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http.headers()
-		}
-	}
-
-	def "headers.contentType"() {
-		setup:
-			loadConfig(ContentTypeOptionsConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['X-Content-Type-Options':'nosniff']
-	}
-
-	@EnableWebSecurity
-	static class ContentTypeOptionsConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-				.headers()
-					.defaultsDisabled()
-					.contentTypeOptions()
-		}
-	}
-
-	def "headers.frameOptions"() {
-		setup:
-			loadConfig(FrameOptionsConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['X-Frame-Options':'DENY']
-	}
-
-	@EnableWebSecurity
-	static class FrameOptionsConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-				.headers()
-					.defaultsDisabled()
-					.frameOptions()
-		}
-	}
-
-	def "headers.hsts"() {
-		setup:
-			loadConfig(HstsConfig)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains']
-	}
-
-	@EnableWebSecurity
-	static class HstsConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-				.headers()
-					.defaultsDisabled()
-					.httpStrictTransportSecurity()
-		}
-	}
-
-	def "headers.cacheControl"() {
-		setup:
-			loadConfig(CacheControlConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
-						'Expires' : '0',
-						'Pragma':'no-cache']
-	}
-
-	@EnableWebSecurity
-	static class CacheControlConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-				.headers()
-					.defaultsDisabled()
-					.cacheControl()
-		}
-	}
-
-	def "headers.xssProtection"() {
-		setup:
-			loadConfig(XssProtectionConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['X-XSS-Protection' : '1; mode=block']
-	}
-
-	@EnableWebSecurity
-	static class XssProtectionConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			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()
-		}
-	}
-
-	def "headers.hpkp no pins"() {
-		setup:
-			loadConfig(HpkpConfigNoPins)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == [:]
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigNoPins extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-		}
-	}
-
-	def "headers.hpkp"() {
-		setup:
-			loadConfig(HpkpConfig)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins-Report-Only' : 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']
-	}
-
-	def "headers.hpkp no secure request"() {
-		setup:
-			loadConfig(HpkpConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-			then:
-			responseHeaders == [:]
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
-		}
-	}
-
-	def "headers.hpkp with pins"() {
-		setup:
-			loadConfig(HpkpConfigWithPins)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins-Report-Only' : 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="']
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigWithPins extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			Map<String, String> pins = new LinkedHashMap<>();
-			pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256");
-			pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256");
-
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.withPins(pins)
-		}
-	}
-
-	def "headers.hpkp custom age"() {
-		setup:
-			loadConfig(HpkpConfigCustomAge)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins-Report-Only' : 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigCustomAge extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
-					.maxAgeInSeconds(604800)
-		}
-	}
-
-	def "headers.hpkp terminate connection"() {
-		setup:
-			loadConfig(HpkpConfigTerminateConnection)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins' : 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="']
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigTerminateConnection extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
-					.reportOnly(false)
-		}
-	}
-
-	def "headers.hpkp include subdomains"() {
-		setup:
-			loadConfig(HpkpConfigIncludeSubDomains)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins-Report-Only' : 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ; includeSubDomains']
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigIncludeSubDomains extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
-					.includeSubDomains(true)
-		}
-	}
-
-	def "headers.hpkp with report URI"() {
-		setup:
-			loadConfig(HpkpConfigWithReportURI)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins-Report-Only' : 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ; report-uri="https://example.net/pkp-report"']
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigWithReportURI extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
-					.reportUri(new URI("https://example.net/pkp-report"))
-		}
-	}
-
-	def "headers.hpkp with report URI as String"() {
-		setup:
-			loadConfig(HpkpConfigWithReportURIAsString)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Public-Key-Pins-Report-Only' : 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" ; report-uri="https://example.net/pkp-report"']
-	}
-
-	@EnableWebSecurity
-	static class HpkpConfigWithReportURIAsString extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpPublicKeyPinning()
-					.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
-					.reportUri("https://example.net/pkp-report")
-		}
-	}
-
-	def "headers.contentSecurityPolicy default header"() {
-		setup:
-			loadConfig(ContentSecurityPolicyDefaultConfig)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Content-Security-Policy': 'default-src \'self\'']
-	}
-
-	@EnableWebSecurity
-	static class ContentSecurityPolicyDefaultConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.contentSecurityPolicy("default-src 'self'");
-		}
-	}
-
-	def "headers.contentSecurityPolicy report-only header"() {
-		setup:
-			loadConfig(ContentSecurityPolicyReportOnlyConfig)
-			request.secure = true
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Content-Security-Policy-Report-Only': 'default-src \'self\'; script-src trustedscripts.example.com']
-	}
-
-	@EnableWebSecurity
-	static class ContentSecurityPolicyReportOnlyConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.contentSecurityPolicy("default-src 'self'; script-src trustedscripts.example.com").reportOnly();
-		}
-	}
-
-	def "headers.contentSecurityPolicy empty policyDirectives"() {
-		when:
-			loadConfig(ContentSecurityPolicyInvalidConfig)
-		then:
-			thrown(BeanCreationException)
-	}
-
-	@EnableWebSecurity
-	static class ContentSecurityPolicyInvalidConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.contentSecurityPolicy("");
-		}
-	}
-
-	def "headers.referrerPolicy default"() {
-		setup:
-			loadConfig(ReferrerPolicyDefaultConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Referrer-Policy': 'no-referrer']
-	}
-
-	@EnableWebSecurity
-	static class ReferrerPolicyDefaultConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.referrerPolicy();
-		}
-	}
-
-	def "headers.referrerPolicy custom"() {
-		setup:
-			loadConfig(ReferrerPolicyCustomConfig)
-		when:
-			springSecurityFilterChain.doFilter(request,response,chain)
-		then:
-			responseHeaders == ['Referrer-Policy': 'same-origin']
-	}
-
-	@EnableWebSecurity
-	static class ReferrerPolicyCustomConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
-		}
-	}
-
-	def "headers.featurePolicy default header"() {
-		setup:
-		    loadConfig(FeaturePolicyDefaultConfig)
-		    request.secure = true
-		when:
-		    springSecurityFilterChain.doFilter(request, response, chain)
-		then:
-		    responseHeaders == ['Feature-Policy': 'geolocation \'self\'']
-	}
-
-	@EnableWebSecurity
-	static class FeaturePolicyDefaultConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.featurePolicy("geolocation 'self'");
-		}
-	}
-
-	def "headers.featurePolicy empty policyDirectives"() {
-		when:
-		loadConfig(FeaturePolicyInvalidConfig)
-		then:
-		thrown(BeanCreationException)
-	}
-
-	@EnableWebSecurity
-	static class FeaturePolicyInvalidConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.featurePolicy("");
-		}
-	}
-
-	@EnableWebSecurity
-	static class HstsWithPreloadConfig extends WebSecurityConfigurerAdapter {
-
-		@Override
-		protected void configure(HttpSecurity http) throws Exception {
-			http
-					.headers()
-					.defaultsDisabled()
-					.httpStrictTransportSecurity()
-					.preload(true)
-		}
-	}
-
-	def "headers.hstsWithPreload"() {
-	    setup:
-		    loadConfig(HstsWithPreloadConfig)
-		    request.secure = true
-	    when:
-		    springSecurityFilterChain.doFilter(request, response, chain)
-	    then:
-		    responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains ; preload']
-	}
-
-}

+ 659 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java

@@ -0,0 +1,659 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.annotation.web.configurers;
+
+import com.google.common.net.HttpHeaders;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+import java.net.URI;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+
+/**
+ * Tests for {@link HeadersConfigurer}.
+ *
+ * @author Rob Winch
+ * @author Tim Ysewyn
+ * @author Joe Grandja
+ * @author Eddú Meléndez
+ * @author Vedran Pavic
+ * @author Eleftheria Stein
+ */
+public class HeadersConfigurerTests {
+
+	@Rule
+	public final SpringTestRule spring = new SpringTestRule();
+
+	@Autowired
+	MockMvc mvc;
+
+	@Test
+	public void getWhenHeadersConfiguredThenDefaultHeadersInResponse() throws Exception {
+		this.spring.register(HeadersConfig.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 HeadersConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndContentTypeConfiguredThenOnlyContentTypeHeaderInResponse()
+			throws Exception {
+		this.spring.register(ContentTypeOptionsConfig.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 ContentTypeOptionsConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.contentTypeOptions();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndFrameOptionsConfiguredThenOnlyFrameOptionsHeaderInResponse()
+			throws Exception {
+		this.spring.register(FrameOptionsConfig.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/"))
+				.andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name()))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_FRAME_OPTIONS);
+	}
+
+	@EnableWebSecurity
+	static class FrameOptionsConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.frameOptions();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndHstsConfiguredThenOnlyStrictTransportSecurityHeaderInResponse()
+			throws Exception {
+		this.spring.register(HstsConfig.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains"))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY);
+	}
+
+	@EnableWebSecurity
+	static class HstsConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpStrictTransportSecurity();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndCacheControlConfiguredThenCacheControlAndExpiresAndPragmaHeadersInResponse()
+			throws Exception {
+		this.spring.register(CacheControlConfig.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 CacheControlConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.cacheControl();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssProtectionHeaderInResponse()
+			throws Exception {
+		this.spring.register(XssProtectionConfig.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 XssProtectionConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.xssProtection();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception {
+		this.spring.register(HeadersCustomSameOriginConfig.class).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name()))
+				.andReturn();
+	}
+
+	@EnableWebSecurity
+	static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.frameOptions().sameOrigin();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndPublicHpkpWithNoPinThenNoHeadersInResponse() throws Exception {
+		this.spring.register(HpkpConfigNoPins.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/"))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).isEmpty();
+	}
+
+	@EnableWebSecurity
+	static class HpkpConfigNoPins extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenSecureRequestAndHpkpWithPinThenPublicKeyPinsReportOnlyHeaderInResponse()
+			throws Exception {
+		this.spring.register(HpkpConfig.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=\""))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY);
+	}
+
+	@Test
+	public void getWhenInsecureRequestHeaderDefaultsDisabledAndHpkpWithPinThenNoHeadersInResponse()
+			throws Exception {
+		this.spring.register(HpkpConfig.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/"))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).isEmpty();
+	}
+
+	@EnableWebSecurity
+	static class HpkpConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=");
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHpkpWithMultiplePinsThenPublicKeyPinsReportOnlyHeaderWithMultiplePinsInResponse()
+			throws Exception {
+		this.spring.register(HpkpConfigWithPins.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=\" ; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY);
+	}
+
+	@EnableWebSecurity
+	static class HpkpConfigWithPins extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			Map<String, String> pins = new LinkedHashMap<>();
+			pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256");
+			pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256");
+
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.withPins(pins);
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHpkpWithCustomAgeThenPublicKeyPinsReportOnlyHeaderWithCustomAgeInResponse() throws Exception {
+		this.spring.register(HpkpConfigCustomAge.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY,
+						"max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY);
+	}
+
+	@EnableWebSecurity
+	static class HpkpConfigCustomAge extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
+						.maxAgeInSeconds(604800);
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHpkpWithReportOnlyFalseThenPublicKeyPinsHeaderInResponse() throws Exception {
+		this.spring.register(HpkpConfigTerminateConnection.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.PUBLIC_KEY_PINS,
+						"max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS);
+	}
+
+	@EnableWebSecurity
+	static class HpkpConfigTerminateConnection extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
+						.reportOnly(false);
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHpkpIncludeSubdomainThenPublicKeyPinsReportOnlyHeaderWithIncludeSubDomainsInResponse()
+			throws Exception {
+		this.spring.register(HpkpConfigIncludeSubDomains.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=\" ; includeSubDomains"))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY);
+	}
+
+	@EnableWebSecurity
+	static class HpkpConfigIncludeSubDomains extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
+						.includeSubDomains(true);
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHpkpWithReportUriThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() throws Exception {
+		this.spring.register(HpkpConfigWithReportURI.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 HpkpConfigWithReportURI extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
+						.reportUri(new URI("https://example.net/pkp-report"));
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHpkpWithReportUriAsStringThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse()
+			throws Exception {
+		this.spring.register(HpkpConfigWithReportURIAsString.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 HpkpConfigWithReportURIAsString extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpPublicKeyPinning()
+						.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=")
+						.reportUri("https://example.net/pkp-report");
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenContentSecurityPolicyConfiguredThenContentSecurityPolicyHeaderInResponse() throws Exception {
+		this.spring.register(ContentSecurityPolicyDefaultConfig.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 ContentSecurityPolicyDefaultConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.contentSecurityPolicy("default-src 'self'");
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenContentSecurityPolicyWithReportOnlyThenContentSecurityPolicyReportOnlyHeaderInResponse() throws Exception {
+		this.spring.register(ContentSecurityPolicyReportOnlyConfig.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 ContentSecurityPolicyReportOnlyConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.contentSecurityPolicy("default-src 'self'; script-src trustedscripts.example.com")
+					.reportOnly();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void configureWhenContentSecurityPolicyEmptyThenException() {
+		assertThatThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidConfig.class).autowire())
+				.isInstanceOf(BeanCreationException.class)
+				.hasRootCauseInstanceOf(IllegalArgumentException.class);
+	}
+
+	@EnableWebSecurity
+	static class ContentSecurityPolicyInvalidConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.contentSecurityPolicy("");
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenReferrerPolicyConfiguredThenReferrerPolicyHeaderInResponse() throws Exception {
+		this.spring.register(ReferrerPolicyDefaultConfig.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 ReferrerPolicyDefaultConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.referrerPolicy();
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenReferrerPolicyConfiguredWithCustomValueThenReferrerPolicyHeaderWithCustomValueInResponse()
+			throws Exception {
+		this.spring.register(ReferrerPolicyCustomConfig.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 ReferrerPolicyCustomConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenFeaturePolicyConfiguredThenFeaturePolicyHeaderInResponse() throws Exception {
+		this.spring.register(FeaturePolicyConfig.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string("Feature-Policy", "geolocation 'self'"))
+				.andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Feature-Policy");
+	}
+
+	@EnableWebSecurity
+	static class FeaturePolicyConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.featurePolicy("geolocation 'self'");
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void configureWhenFeaturePolicyEmptyThenException() {
+		assertThatThrownBy(() -> this.spring.register(FeaturePolicyInvalidConfig.class).autowire())
+				.isInstanceOf(BeanCreationException.class)
+				.hasRootCauseInstanceOf(IllegalArgumentException.class);
+	}
+
+	@EnableWebSecurity
+	static class FeaturePolicyInvalidConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.featurePolicy("");
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void getWhenHstsConfiguredWithPreloadThenStrictTransportSecurityHeaderWithPreloadInResponse()
+			throws Exception {
+		this.spring.register(HstsWithPreloadConfig.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 HstsWithPreloadConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.httpStrictTransportSecurity()
+						.preload(true);
+			// @formatter:on
+		}
+	}
+}