Explorar o código

Merge branch '5.8.x'

Steve Riesenberg %!s(int64=2) %!d(string=hai) anos
pai
achega
76fbca9f46
Modificáronse 13 ficheiros con 434 adicións e 54 borrados
  1. 40 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java
  2. 12 0
      config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
  3. 7 1
      config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/XssProtectionConfigDsl.kt
  4. 7 1
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDsl.kt
  5. 56 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java
  6. 3 3
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java
  7. 46 1
      config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java
  8. 29 0
      config/src/test/kotlin/org/springframework/security/config/annotation/web/headers/XssProtectionConfigDslTests.kt
  9. 26 1
      config/src/test/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDslTests.kt
  10. 69 23
      web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java
  11. 75 21
      web/src/main/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriter.java
  12. 31 1
      web/src/test/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriterTests.java
  13. 33 1
      web/src/test/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriterTests.java

+ 40 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

@@ -729,7 +729,10 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 		 * If false, will not specify the mode as blocked. In this instance, any content
 		 * will be attempted to be fixed. If true, the content will be replaced with "#".
 		 * @param enabled the new value
+		 * @deprecated use
+		 * {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
 		 */
+		@Deprecated
 		public XXssConfig block(boolean enabled) {
 			this.writer.setBlock(enabled);
 			return this;
@@ -757,12 +760,49 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 		 * X-XSS-Protection: 0
 		 * </pre>
 		 * @param enabled the new value
+		 * @deprecated use
+		 * {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
 		 */
+		@Deprecated
 		public XXssConfig xssProtectionEnabled(boolean enabled) {
 			this.writer.setEnabled(enabled);
 			return this;
 		}
 
+		/**
+		 * Sets the value of the X-XSS-PROTECTION header. OWASP recommends using
+		 * {@link XXssProtectionHeaderWriter.HeaderValue#DISABLED}.
+		 *
+		 * If {@link XXssProtectionHeaderWriter.HeaderValue#DISABLED}, will specify that
+		 * X-XSS-Protection is disabled. For example:
+		 *
+		 * <pre>
+		 * X-XSS-Protection: 0
+		 * </pre>
+		 *
+		 * If {@link XXssProtectionHeaderWriter.HeaderValue#ENABLED}, will contain a value
+		 * of 1, but will not specify the mode as blocked. In this instance, any content
+		 * will be attempted to be fixed. For example:
+		 *
+		 * <pre>
+		 * X-XSS-Protection: 1
+		 * </pre>
+		 *
+		 * If {@link XXssProtectionHeaderWriter.HeaderValue#ENABLED_MODE_BLOCK}, will
+		 * contain a value of 1 and will specify mode as blocked. The content will be
+		 * replaced with "#". For example:
+		 *
+		 * <pre>
+		 * X-XSS-Protection: 1 ; mode=block
+		 * </pre>
+		 * @param headerValue the new header value
+		 * @since 5.8
+		 */
+		public XXssConfig headerValue(XXssProtectionHeaderWriter.HeaderValue headerValue) {
+			this.writer.setHeaderValue(headerValue);
+			return this;
+		}
+
 		/**
 		 * Disables X-XSS-Protection header (does not include it)
 		 * @return the {@link HeadersConfigurer} for additional configuration

+ 12 - 0
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -2861,6 +2861,18 @@ public class ServerHttpSecurity {
 				return HeaderSpec.this;
 			}
 
+			/**
+			 * Sets the value of x-xss-protection header. OWASP recommends using
+			 * {@link XXssProtectionServerHttpHeadersWriter.HeaderValue#DISABLED}.
+			 * @param headerValue the headerValue
+			 * @return the {@link HeaderSpec} to continue configuring
+			 * @since 5.8
+			 */
+			public HeaderSpec headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue headerValue) {
+				HeaderSpec.this.xss.setHeaderValue(headerValue);
+				return HeaderSpec.this;
+			}
+
 		}
 
 		/**

+ 7 - 1
config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/XssProtectionConfigDsl.kt

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.web.headers
 
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue
 
 /**
  * A Kotlin DSL to configure the [HttpSecurity] XSS protection header using
@@ -28,11 +29,15 @@ import org.springframework.security.config.annotation.web.configurers.HeadersCon
  * @property block whether to specify the mode as blocked
  * @property xssProtectionEnabled if true, the header value will contain a value of 1.
  * If false, will explicitly disable specify that X-XSS-Protection is disabled.
+ * @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
  */
 @HeadersSecurityMarker
 class XssProtectionConfigDsl {
+    @Deprecated("use headerValue instead")
     var block: Boolean? = null
+    @Deprecated("use headerValue instead")
     var xssProtectionEnabled: Boolean? = null
+    var headerValue: HeaderValue? = null
 
     private var disabled = false
 
@@ -47,6 +52,7 @@ class XssProtectionConfigDsl {
         return { xssProtection ->
             block?.also { xssProtection.block(block!!) }
             xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) }
+            headerValue?.also { xssProtection.headerValue(headerValue) }
 
             if (disabled) {
                 xssProtection.disable()

+ 7 - 1
config/src/main/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDsl.kt

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -16,16 +16,21 @@
 
 package org.springframework.security.config.web.server
 
+import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter.HeaderValue
+
 /**
  * A Kotlin DSL to configure the [ServerHttpSecurity] XSS protection header using
  * idiomatic Kotlin code.
  *
+ * @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
+ *
  * @author Eleftheria Stein
  * @since 5.4
  */
 @ServerSecurityMarker
 class ServerXssProtectionDsl {
     private var disabled = false
+    var headerValue: HeaderValue? = null
 
     /**
      * Disables cache control response headers
@@ -36,6 +41,7 @@ class ServerXssProtectionDsl {
 
     internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
         return { xss ->
+            headerValue?.also { xss.headerValue(headerValue) }
             if (disabled) {
                 xss.disable()
             }

+ 56 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -38,6 +38,7 @@ import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicy
 import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
 import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
+import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
@@ -59,6 +60,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * @author Vedran Pavic
  * @author Eleftheria Stein
  * @author Marcus Da Coregio
+ * @author Daniel Garnier-Moiroux
  */
 @ExtendWith(SpringTestContextExtension.class)
 public class HeadersConfigurerTests {
@@ -172,6 +174,15 @@ public class HeadersConfigurerTests {
 		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
 	}
 
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueDisabledThenOnlyXssProtectionHeaderInResponse()
+			throws Exception {
+		this.spring.register(XssProtectionValueDisabledConfig.class).autowire();
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")).andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
+	}
+
 	@Test
 	public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception {
 		this.spring.register(XssProtectionInLambdaConfig.class).autowire();
@@ -180,6 +191,15 @@ public class HeadersConfigurerTests {
 		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
 	}
 
+	@Test
+	public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueDisabledInLambdaThenOnlyXssProtectionHeaderInResponse()
+			throws Exception {
+		this.spring.register(XssProtectionValueDisabledInLambdaConfig.class).autowire();
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")).andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
+	}
+
 	@Test
 	public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception {
 		this.spring.register(HeadersCustomSameOriginConfig.class).autowire();
@@ -690,6 +710,22 @@ public class HeadersConfigurerTests {
 	}
 
 	@Configuration
+	@EnableWebSecurity
+	static class XssProtectionValueDisabledConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.xssProtection()
+					.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED);
+			// @formatter:on
+		}
+
+	}
+
 	@EnableWebSecurity
 	static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter {
 
@@ -708,6 +744,25 @@ public class HeadersConfigurerTests {
 	}
 
 	@Configuration
+	@EnableWebSecurity
+	static class XssProtectionValueDisabledInLambdaConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers((headers) ->
+					headers
+						.defaultsDisabled()
+						.xssProtection((xXssConfig) ->
+							xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED)
+						)
+				);
+			// @formatter:on
+		}
+
+	}
+
 	@EnableWebSecurity
 	static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter {
 

+ 3 - 3
config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -32,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.web.header.writers.StaticHeadersWriter;
+import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
 import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy;
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
 import org.springframework.security.web.util.matcher.AnyRequestMatcher;
@@ -282,8 +283,7 @@ public class NamespaceHttpHeadersTests {
 					// xss-protection@enabled and xss-protection@block
 					.defaultsDisabled()
 					.xssProtection()
-						.xssProtectionEnabled(true)
-						.block(false);
+						.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED);
 			// @formatter:on
 		}
 

+ 46 - 1
config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -296,6 +296,51 @@ public class HeaderSpecTests {
 		assertHeaders();
 	}
 
+	@Test
+	public void headersWhenXssProtectionValueDisabledThenXssProtectionWritten() {
+		this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0");
+		// @formatter:off
+		this.http.headers()
+				.xssProtection()
+				.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED);
+		// @formatter:on
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenXssProtectionValueEnabledThenXssProtectionWritten() {
+		this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1");
+		// @formatter:off
+		this.http.headers()
+				.xssProtection()
+				.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED);
+		// @formatter:on
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() {
+		this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
+		// @formatter:off
+		this.http.headers()
+				.xssProtection()
+				.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK);
+		// @formatter:on
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenXssProtectionValueDisabledInLambdaThenXssProtectionWritten() {
+		this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0");
+		// @formatter:off
+		this.http.headers()
+				.xssProtection((xssProtection) ->
+						xssProtection.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED)
+				);
+		// @formatter:on
+		assertHeaders();
+	}
+
 	@Test
 	public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() {
 		String policyDirectives = "Feature-Policy";

+ 29 - 0
config/src/test/kotlin/org/springframework/security/config/annotation/web/headers/XssProtectionConfigDslTests.kt

@@ -27,6 +27,7 @@ import org.springframework.security.config.annotation.web.invoke
 import org.springframework.security.config.test.SpringTestContext
 import org.springframework.security.config.test.SpringTestContextExtension
 import org.springframework.security.web.SecurityFilterChain
+import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter
 import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
 import org.springframework.test.web.servlet.MockMvc
 import org.springframework.test.web.servlet.get
@@ -152,4 +153,32 @@ class XssProtectionConfigDslTests {
             return http.build()
         }
     }
+
+    @Test
+    fun `headers when XSS protection header value disabled then X-XSS-Protection header is 0`() {
+        this.spring.register(XssProtectionHeaderValueDisabledFunctionConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0") }
+        }
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    open class XssProtectionHeaderValueDisabledFunctionConfig () {
+        @Bean
+        open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    xssProtection {
+                        headerValue = XXssProtectionHeaderWriter.HeaderValue.DISABLED
+                    }
+                }
+            }
+            return http.build()
+        }
+    }
 }

+ 26 - 1
config/src/test/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDslTests.kt

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -99,4 +99,29 @@ class ServerXssProtectionDslTests {
             }
         }
     }
+
+    @Test
+    fun `request when xss protection value disabled then xss header in response`() {
+        this.spring.register(XssValueDisabledConfig::class.java).autowire()
+
+        this.client.get()
+                .uri("/")
+                .exchange()
+                .expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0")
+    }
+
+    @EnableWebFluxSecurity
+    @EnableWebFlux
+    open class XssValueDisabledConfig {
+        @Bean
+        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+            return http {
+                headers {
+                    xssProtection {
+                        headerValue = XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED
+                    }
+                }
+            }
+        }
+    }
 }

+ 69 - 23
web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -20,6 +20,7 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
 import org.springframework.security.web.header.HeaderWriter;
+import org.springframework.util.Assert;
 
 /**
  * Renders the <a href=
@@ -34,25 +35,19 @@ public final class XXssProtectionHeaderWriter implements HeaderWriter {
 
 	private static final String XSS_PROTECTION_HEADER = "X-XSS-Protection";
 
-	private boolean enabled;
-
-	private boolean block;
-
-	private String headerValue;
+	private HeaderValue headerValue;
 
 	/**
 	 * Create a new instance
 	 */
 	public XXssProtectionHeaderWriter() {
-		this.enabled = true;
-		this.block = true;
-		updateHeaderValue();
+		this.headerValue = HeaderValue.ENABLED_MODE_BLOCK;
 	}
 
 	@Override
 	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
 		if (!response.containsHeader(XSS_PROTECTION_HEADER)) {
-			response.setHeader(XSS_PROTECTION_HEADER, this.headerValue);
+			response.setHeader(XSS_PROTECTION_HEADER, this.headerValue.toString());
 		}
 	}
 
@@ -77,37 +72,88 @@ public final class XXssProtectionHeaderWriter implements HeaderWriter {
 	 * X-XSS-Protection: 0
 	 * </pre>
 	 * @param enabled the new value
+	 * @deprecated use {@link XXssProtectionHeaderWriter#setHeaderValue(HeaderValue)}
+	 * instead
 	 */
+	@Deprecated
 	public void setEnabled(boolean enabled) {
 		if (!enabled) {
-			setBlock(false);
+			this.headerValue = HeaderValue.DISABLED;
+		}
+		else if (this.headerValue == HeaderValue.DISABLED) {
+			this.headerValue = HeaderValue.ENABLED;
 		}
-		this.enabled = enabled;
-		updateHeaderValue();
 	}
 
 	/**
 	 * If false, will not specify the mode as blocked. In this instance, any content will
 	 * be attempted to be fixed. If true, the content will be replaced with "#".
 	 * @param block the new value
+	 * @deprecated use {@link XXssProtectionHeaderWriter#setHeaderValue(HeaderValue)}
+	 * instead
 	 */
+	@Deprecated
 	public void setBlock(boolean block) {
-		if (!this.enabled && block) {
+		if (this.headerValue == HeaderValue.DISABLED && block) {
 			throw new IllegalArgumentException("Cannot set block to true with enabled false");
 		}
-		this.block = block;
-		updateHeaderValue();
+		this.headerValue = block ? HeaderValue.ENABLED_MODE_BLOCK : HeaderValue.ENABLED;
 	}
 
-	private void updateHeaderValue() {
-		if (!this.enabled) {
-			this.headerValue = "0";
-			return;
+	/**
+	 * Sets the value of the X-XSS-PROTECTION header.
+	 * <p>
+	 * If {@link HeaderValue#DISABLED}, will specify that X-XSS-Protection is disabled.
+	 * For example:
+	 *
+	 * <pre>
+	 * X-XSS-Protection: 0
+	 * </pre>
+	 * <p>
+	 * If {@link HeaderValue#ENABLED}, will contain a value of 1, but will not specify the
+	 * mode as blocked. In this instance, any content will be attempted to be fixed. For
+	 * example:
+	 *
+	 * <pre>
+	 * X-XSS-Protection: 1
+	 * </pre>
+	 * <p>
+	 * If {@link HeaderValue#ENABLED_MODE_BLOCK}, will contain a value of 1 and will
+	 * specify mode as blocked. The content will be replaced with "#". For example:
+	 *
+	 * <pre>
+	 * X-XSS-Protection: 1 ; mode=block
+	 * </pre>
+	 * @param headerValue the new header value
+	 * @throws IllegalArgumentException when headerValue is null
+	 * @since 5.8
+	 */
+	public void setHeaderValue(HeaderValue headerValue) {
+		Assert.notNull(headerValue, "headerValue cannot be null");
+		this.headerValue = headerValue;
+	}
+
+	/**
+	 * The value of the x-xss-protection header. One of: "0", "1", "1 ; mode=block"
+	 *
+	 * @author Daniel Garnier-Moiroux
+	 * @since 5.8
+	 */
+	public enum HeaderValue {
+
+		DISABLED("0"), ENABLED("1"), ENABLED_MODE_BLOCK("1; mode=block");
+
+		private final String value;
+
+		HeaderValue(String value) {
+			this.value = value;
 		}
-		this.headerValue = "1";
-		if (this.block) {
-			this.headerValue += "; mode=block";
+
+		@Override
+		public String toString() {
+			return this.value;
 		}
+
 	}
 
 	@Override

+ 75 - 21
web/src/main/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -26,24 +26,22 @@ import org.springframework.web.server.ServerWebExchange;
  * Add the x-xss-protection header.
  *
  * @author Rob Winch
+ * @author Daniel Garnier-Moiroux
  * @since 5.0
  */
 public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersWriter {
 
 	public static final String X_XSS_PROTECTION = "X-XSS-Protection";
 
-	private boolean enabled;
-
-	private boolean block;
-
 	private ServerHttpHeadersWriter delegate;
 
+	private HeaderValue headerValue;
+
 	/**
 	 * Creates a new instance
 	 */
 	public XXssProtectionServerHttpHeadersWriter() {
-		this.enabled = true;
-		this.block = true;
+		this.headerValue = HeaderValue.ENABLED_MODE_BLOCK;
 		updateDelegate();
 	}
 
@@ -73,12 +71,17 @@ public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersW
 	 * X-XSS-Protection: 0
 	 * </pre>
 	 * @param enabled the new value
+	 * @deprecated use
+	 * {@link XXssProtectionServerHttpHeadersWriter#setHeaderValue(HeaderValue)} instead
 	 */
+	@Deprecated
 	public void setEnabled(boolean enabled) {
 		if (!enabled) {
-			setBlock(false);
+			this.headerValue = HeaderValue.DISABLED;
+		}
+		else if (this.headerValue == HeaderValue.DISABLED) {
+			this.headerValue = HeaderValue.ENABLED;
 		}
-		this.enabled = enabled;
 		updateDelegate();
 	}
 
@@ -86,27 +89,78 @@ public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersW
 	 * If false, will not specify the mode as blocked. In this instance, any content will
 	 * be attempted to be fixed. If true, the content will be replaced with "#".
 	 * @param block the new value
+	 * @deprecated use
+	 * {@link XXssProtectionServerHttpHeadersWriter#setHeaderValue(HeaderValue)} instead
 	 */
+	@Deprecated
 	public void setBlock(boolean block) {
-		Assert.isTrue(this.enabled || !block, "Cannot set block to true with enabled false");
-		this.block = block;
+		Assert.isTrue(this.headerValue != HeaderValue.DISABLED || !block,
+				"Cannot set block to true with enabled false");
+		this.headerValue = block ? HeaderValue.ENABLED_MODE_BLOCK : HeaderValue.ENABLED;
 		updateDelegate();
 	}
 
-	private void updateDelegate() {
-		Builder builder = StaticServerHttpHeadersWriter.builder();
-		builder.header(X_XSS_PROTECTION, createHeaderValue());
-		this.delegate = builder.build();
+	/**
+	 * Sets the value of the X-XSS-PROTECTION header.
+	 * <p>
+	 * If {@link HeaderValue#DISABLED}, will specify that X-XSS-Protection is disabled.
+	 * For example:
+	 *
+	 * <pre>
+	 * X-XSS-Protection: 0
+	 * </pre>
+	 * <p>
+	 * If {@link HeaderValue#ENABLED}, will contain a value of 1, but will not specify the
+	 * mode as blocked. In this instance, any content will be attempted to be fixed. For
+	 * example:
+	 *
+	 * <pre>
+	 * X-XSS-Protection: 1
+	 * </pre>
+	 * <p>
+	 * If {@link HeaderValue#ENABLED_MODE_BLOCK}, will contain a value of 1 and will
+	 * specify mode as blocked. The content will be replaced with "#". For example:
+	 *
+	 * <pre>
+	 * X-XSS-Protection: 1 ; mode=block
+	 * </pre>
+	 * @param headerValue the new headerValue
+	 * @throws IllegalArgumentException if headerValue is null
+	 * @since 5.8
+	 */
+	public void setHeaderValue(HeaderValue headerValue) {
+		Assert.notNull(headerValue, "headerValue cannot be null");
+		this.headerValue = headerValue;
+		updateDelegate();
 	}
 
-	private String createHeaderValue() {
-		if (!this.enabled) {
-			return "0";
+	/**
+	 * The value of the x-xss-protection header. One of: "0", "1", "1 ; mode=block"
+	 *
+	 * @author Daniel Garnier-Moiroux
+	 * @since 5.8
+	 */
+	public enum HeaderValue {
+
+		DISABLED("0"), ENABLED("1"), ENABLED_MODE_BLOCK("1 ; mode=block");
+
+		private final String value;
+
+		HeaderValue(String value) {
+			this.value = value;
 		}
-		if (!this.block) {
-			return "1";
+
+		@Override
+		public String toString() {
+			return this.value;
 		}
-		return "1 ; mode=block";
+
+	}
+
+	private void updateDelegate() {
+		Builder builder = StaticServerHttpHeadersWriter.builder();
+		builder.header(X_XSS_PROTECTION, this.headerValue.toString());
+		this.delegate = builder.build();
 	}
 
 }

+ 31 - 1
web/src/test/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriterTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -94,4 +94,34 @@ public class XXssProtectionHeaderWriterTests {
 		assertThat(this.response.getHeader(XSS_PROTECTION_HEADER)).isSameAs(value);
 	}
 
+	@Test
+	void writeHeaderWhenDisabled() {
+		this.writer.setHeaderValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeaderValues("X-XSS-Protection")).containsOnly("0");
+	}
+
+	@Test
+	void writeHeaderWhenEnabled() {
+		this.writer.setHeaderValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeaderValues("X-XSS-Protection")).containsOnly("1");
+	}
+
+	@Test
+	void writeHeaderWhenEnabledModeBlock() {
+		this.writer.setHeaderValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeaderValues("X-XSS-Protection")).containsOnly("1; mode=block");
+	}
+
+	@Test
+	public void setHeaderValueNullThenThrowsIllegalArgumentException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setHeaderValue(null))
+				.withMessage("headerValue cannot be null");
+	}
+
 }

+ 33 - 1
web/src/test/java/org/springframework/security/web/server/header/XXssProtectionServerHttpHeadersWriterTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -24,6 +24,7 @@ import org.springframework.mock.web.server.MockServerWebExchange;
 import org.springframework.web.server.ServerWebExchange;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 
 /**
  * @author Rob Winch
@@ -37,6 +38,12 @@ public class XXssProtectionServerHttpHeadersWriterTests {
 
 	XXssProtectionServerHttpHeadersWriter writer = new XXssProtectionServerHttpHeadersWriter();
 
+	@Test
+	void setHeaderValueNullThenThrowsIllegalArgumentException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setHeaderValue(null))
+				.withMessage("headerValue cannot be null");
+	}
+
 	@Test
 	public void writeHeadersWhenNoHeadersThenWriteHeaders() {
 		this.writer.writeHttpHeaders(this.exchange);
@@ -70,4 +77,29 @@ public class XXssProtectionServerHttpHeadersWriterTests {
 		assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly(headerValue);
 	}
 
+	@Test
+	void writeHeadersWhenDisabledThenWriteHeaders() {
+		this.writer.setHeaderValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED);
+		this.writer.writeHttpHeaders(this.exchange);
+		assertThat(this.headers).hasSize(1);
+		assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly("0");
+	}
+
+	@Test
+	void writeHeadersWhenEnabledThenWriteHeaders() {
+		this.writer.setHeaderValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED);
+		this.writer.writeHttpHeaders(this.exchange);
+		assertThat(this.headers).hasSize(1);
+		assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly("1");
+	}
+
+	@Test
+	void writeHeadersWhenEnabledModeBlockThenWriteHeaders() {
+		this.writer.setHeaderValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK);
+		this.writer.writeHttpHeaders(this.exchange);
+		assertThat(this.headers).hasSize(1);
+		assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION))
+				.containsOnly("1 ; mode=block");
+	}
+
 }