Merge branch '5.8.x'

This commit is contained in:
Steve Riesenberg 2022-09-30 09:50:02 -05:00
commit 76fbca9f46
No known key found for this signature in database
GPG Key ID: 5F311AB48A55D521
13 changed files with 435 additions and 55 deletions

View File

@ -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 * 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 "#". * will be attempted to be fixed. If true, the content will be replaced with "#".
* @param enabled the new value * @param enabled the new value
* @deprecated use
* {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
*/ */
@Deprecated
public XXssConfig block(boolean enabled) { public XXssConfig block(boolean enabled) {
this.writer.setBlock(enabled); this.writer.setBlock(enabled);
return this; return this;
@ -757,12 +760,49 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
* X-XSS-Protection: 0 * X-XSS-Protection: 0
* </pre> * </pre>
* @param enabled the new value * @param enabled the new value
* @deprecated use
* {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
*/ */
@Deprecated
public XXssConfig xssProtectionEnabled(boolean enabled) { public XXssConfig xssProtectionEnabled(boolean enabled) {
this.writer.setEnabled(enabled); this.writer.setEnabled(enabled);
return this; 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) * Disables X-XSS-Protection header (does not include it)
* @return the {@link HeadersConfigurer} for additional configuration * @return the {@link HeadersConfigurer} for additional configuration

View File

@ -2861,6 +2861,18 @@ public class ServerHttpSecurity {
return HeaderSpec.this; 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;
}
} }
/** /**

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer 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 * 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 block whether to specify the mode as blocked
* @property xssProtectionEnabled if true, the header value will contain a value of 1. * @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. * 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 @HeadersSecurityMarker
class XssProtectionConfigDsl { class XssProtectionConfigDsl {
@Deprecated("use headerValue instead")
var block: Boolean? = null var block: Boolean? = null
@Deprecated("use headerValue instead")
var xssProtectionEnabled: Boolean? = null var xssProtectionEnabled: Boolean? = null
var headerValue: HeaderValue? = null
private var disabled = false private var disabled = false
@ -47,6 +52,7 @@ class XssProtectionConfigDsl {
return { xssProtection -> return { xssProtection ->
block?.also { xssProtection.block(block!!) } block?.also { xssProtection.block(block!!) }
xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) } xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) }
headerValue?.also { xssProtection.headerValue(headerValue) }
if (disabled) { if (disabled) {
xssProtection.disable() xssProtection.disable()

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 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 * A Kotlin DSL to configure the [ServerHttpSecurity] XSS protection header using
* idiomatic Kotlin code. * idiomatic Kotlin code.
* *
* @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
*
* @author Eleftheria Stein * @author Eleftheria Stein
* @since 5.4 * @since 5.4
*/ */
@ServerSecurityMarker @ServerSecurityMarker
class ServerXssProtectionDsl { class ServerXssProtectionDsl {
private var disabled = false private var disabled = false
var headerValue: HeaderValue? = null
/** /**
* Disables cache control response headers * Disables cache control response headers
@ -36,6 +41,7 @@ class ServerXssProtectionDsl {
internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit { internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
return { xss -> return { xss ->
headerValue?.also { xss.headerValue(headerValue) }
if (disabled) { if (disabled) {
xss.disable() xss.disable()
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.CrossOriginOpenerPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter; import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy; 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.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
@ -59,6 +60,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Vedran Pavic * @author Vedran Pavic
* @author Eleftheria Stein * @author Eleftheria Stein
* @author Marcus Da Coregio * @author Marcus Da Coregio
* @author Daniel Garnier-Moiroux
*/ */
@ExtendWith(SpringTestContextExtension.class) @ExtendWith(SpringTestContextExtension.class)
public class HeadersConfigurerTests { public class HeadersConfigurerTests {
@ -172,6 +174,15 @@ public class HeadersConfigurerTests {
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); 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 @Test
public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception { public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception {
this.spring.register(XssProtectionInLambdaConfig.class).autowire(); this.spring.register(XssProtectionInLambdaConfig.class).autowire();
@ -180,6 +191,15 @@ public class HeadersConfigurerTests {
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); 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 @Test
public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception { public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception {
this.spring.register(HeadersCustomSameOriginConfig.class).autowire(); this.spring.register(HeadersCustomSameOriginConfig.class).autowire();
@ -690,6 +710,22 @@ public class HeadersConfigurerTests {
} }
@Configuration @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 @EnableWebSecurity
static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter { static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter {
@ -708,6 +744,25 @@ public class HeadersConfigurerTests {
} }
@Configuration @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 @EnableWebSecurity
static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter { static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter {

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.header.writers.StaticHeadersWriter; 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.StaticAllowFromStrategy;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher;
@ -282,8 +283,7 @@ public class NamespaceHttpHeadersTests {
// xss-protection@enabled and xss-protection@block // xss-protection@enabled and xss-protection@block
.defaultsDisabled() .defaultsDisabled()
.xssProtection() .xssProtection()
.xssProtectionEnabled(true) .headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED);
.block(false);
// @formatter:on // @formatter:on
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -296,6 +296,51 @@ public class HeaderSpecTests {
assertHeaders(); 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 @Test
public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() { public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() {
String policyDirectives = "Feature-Policy"; String policyDirectives = "Feature-Policy";

View File

@ -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.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.web.SecurityFilterChain 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.security.web.server.header.XXssProtectionServerHttpHeadersWriter
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.get
@ -152,4 +153,32 @@ class XssProtectionConfigDslTests {
return http.build() 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()
}
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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
}
}
}
}
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.HeaderWriter; import org.springframework.security.web.header.HeaderWriter;
import org.springframework.util.Assert;
/** /**
* Renders the <a href= * 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 static final String XSS_PROTECTION_HEADER = "X-XSS-Protection";
private boolean enabled; private HeaderValue headerValue;
private boolean block;
private String headerValue;
/** /**
* Create a new instance * Create a new instance
*/ */
public XXssProtectionHeaderWriter() { public XXssProtectionHeaderWriter() {
this.enabled = true; this.headerValue = HeaderValue.ENABLED_MODE_BLOCK;
this.block = true;
updateHeaderValue();
} }
@Override @Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (!response.containsHeader(XSS_PROTECTION_HEADER)) { 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 * X-XSS-Protection: 0
* </pre> * </pre>
* @param enabled the new value * @param enabled the new value
* @deprecated use {@link XXssProtectionHeaderWriter#setHeaderValue(HeaderValue)}
* instead
*/ */
@Deprecated
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (!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 * 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 "#". * be attempted to be fixed. If true, the content will be replaced with "#".
* @param block the new value * @param block the new value
* @deprecated use {@link XXssProtectionHeaderWriter#setHeaderValue(HeaderValue)}
* instead
*/ */
@Deprecated
public void setBlock(boolean block) { 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"); throw new IllegalArgumentException("Cannot set block to true with enabled false");
} }
this.block = block; this.headerValue = block ? HeaderValue.ENABLED_MODE_BLOCK : HeaderValue.ENABLED;
updateHeaderValue();
} }
private void updateHeaderValue() { /**
if (!this.enabled) { * Sets the value of the X-XSS-PROTECTION header.
this.headerValue = "0"; * <p>
return; * 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) { @Override
this.headerValue += "; mode=block"; public String toString() {
return this.value;
} }
} }
@Override @Override

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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. * Add the x-xss-protection header.
* *
* @author Rob Winch * @author Rob Winch
* @author Daniel Garnier-Moiroux
* @since 5.0 * @since 5.0
*/ */
public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersWriter { public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersWriter {
public static final String X_XSS_PROTECTION = "X-XSS-Protection"; public static final String X_XSS_PROTECTION = "X-XSS-Protection";
private boolean enabled;
private boolean block;
private ServerHttpHeadersWriter delegate; private ServerHttpHeadersWriter delegate;
private HeaderValue headerValue;
/** /**
* Creates a new instance * Creates a new instance
*/ */
public XXssProtectionServerHttpHeadersWriter() { public XXssProtectionServerHttpHeadersWriter() {
this.enabled = true; this.headerValue = HeaderValue.ENABLED_MODE_BLOCK;
this.block = true;
updateDelegate(); updateDelegate();
} }
@ -73,12 +71,17 @@ public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersW
* X-XSS-Protection: 0 * X-XSS-Protection: 0
* </pre> * </pre>
* @param enabled the new value * @param enabled the new value
* @deprecated use
* {@link XXssProtectionServerHttpHeadersWriter#setHeaderValue(HeaderValue)} instead
*/ */
@Deprecated
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (!enabled) { if (!enabled) {
setBlock(false); this.headerValue = HeaderValue.DISABLED;
}
else if (this.headerValue == HeaderValue.DISABLED) {
this.headerValue = HeaderValue.ENABLED;
} }
this.enabled = enabled;
updateDelegate(); 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 * 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 "#". * be attempted to be fixed. If true, the content will be replaced with "#".
* @param block the new value * @param block the new value
* @deprecated use
* {@link XXssProtectionServerHttpHeadersWriter#setHeaderValue(HeaderValue)} instead
*/ */
@Deprecated
public void setBlock(boolean block) { public void setBlock(boolean block) {
Assert.isTrue(this.enabled || !block, "Cannot set block to true with enabled false"); Assert.isTrue(this.headerValue != HeaderValue.DISABLED || !block,
this.block = block; "Cannot set block to true with enabled false");
this.headerValue = block ? HeaderValue.ENABLED_MODE_BLOCK : HeaderValue.ENABLED;
updateDelegate(); updateDelegate();
} }
/**
* 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();
}
/**
* 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;
}
@Override
public String toString() {
return this.value;
}
}
private void updateDelegate() { private void updateDelegate() {
Builder builder = StaticServerHttpHeadersWriter.builder(); Builder builder = StaticServerHttpHeadersWriter.builder();
builder.header(X_XSS_PROTECTION, createHeaderValue()); builder.header(X_XSS_PROTECTION, this.headerValue.toString());
this.delegate = builder.build(); this.delegate = builder.build();
} }
private String createHeaderValue() {
if (!this.enabled) {
return "0";
}
if (!this.block) {
return "1";
}
return "1 ; mode=block";
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); 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");
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/** /**
* @author Rob Winch * @author Rob Winch
@ -37,6 +38,12 @@ public class XXssProtectionServerHttpHeadersWriterTests {
XXssProtectionServerHttpHeadersWriter writer = new XXssProtectionServerHttpHeadersWriter(); XXssProtectionServerHttpHeadersWriter writer = new XXssProtectionServerHttpHeadersWriter();
@Test
void setHeaderValueNullThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setHeaderValue(null))
.withMessage("headerValue cannot be null");
}
@Test @Test
public void writeHeadersWhenNoHeadersThenWriteHeaders() { public void writeHeadersWhenNoHeadersThenWriteHeaders() {
this.writer.writeHttpHeaders(this.exchange); this.writer.writeHttpHeaders(this.exchange);
@ -70,4 +77,29 @@ public class XXssProtectionServerHttpHeadersWriterTests {
assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)).containsOnly(headerValue); 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");
}
} }