From e48d6b039baf104a6c56f4e62e0c2ff4d35a22bb Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:36:06 -0500 Subject: [PATCH] Support ServerWebExchangeFirewall @Bean Closes gh-15974 --- .../WebFluxSecurityConfiguration.java | 8 +++- .../WebFluxSecurityConfigurationTests.java | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java index 07119e8ee0..9796e9349e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.web.reactive; import java.util.Arrays; import java.util.List; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.ApplicationContext; @@ -30,6 +31,7 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.result.view.AbstractView; @@ -65,8 +67,10 @@ class WebFluxSecurityConfiguration { @Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME) @Order(WEB_FILTER_CHAIN_FILTER_ORDER) - WebFilterChainProxy springSecurityWebFilterChainFilter() { - return new WebFilterChainProxy(getSecurityWebFilterChains()); + WebFilterChainProxy springSecurityWebFilterChainFilter(ObjectProvider firewall) { + WebFilterChainProxy webFilterChainProxy = new WebFilterChainProxy(getSecurityWebFilterChains()); + firewall.ifUnique(webFilterChainProxy::setFirewall); + return webFilterChainProxy; } @Bean(name = AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java index 3fb9548e4b..9c49fb7a82 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java @@ -16,14 +16,24 @@ package org.springframework.security.config.annotation.web.reactive; +import java.util.Collections; + +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import reactor.core.publisher.Mono; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall; +import org.springframework.web.server.handler.DefaultWebFilterChain; import static org.assertj.core.api.Assertions.assertThat; @@ -45,6 +55,28 @@ public class WebFluxSecurityConfigurationTests { assertThat(webFilterChainProxy).isNotNull(); } + @Test + void loadConfigWhenDefaultThenFirewalled() throws Exception { + this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, + WebFluxSecurityConfiguration.class).autowire(); + WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); + MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); + DefaultWebFilterChain chain = emptyChain(); + webFilterChainProxy.filter(exchange, chain).block(); + assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void loadConfigWhenFirewallBeanThenCustomized() throws Exception { + this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, + WebFluxSecurityConfiguration.class, NoOpFirewallConfig.class).autowire(); + WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); + MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); + DefaultWebFilterChain chain = emptyChain(); + webFilterChainProxy.filter(exchange, chain).block(); + assertThat(exchange.getResponse().getStatusCode()).isNotEqualTo(HttpStatus.BAD_REQUEST); + } + @Test public void loadConfigWhenBeanProxyingEnabledAndSubclassThenWebFilterChainProxyExists() { this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, @@ -53,6 +85,20 @@ public class WebFluxSecurityConfigurationTests { assertThat(webFilterChainProxy).isNotNull(); } + private static @NotNull DefaultWebFilterChain emptyChain() { + return new DefaultWebFilterChain((webExchange) -> Mono.empty(), Collections.emptyList()); + } + + @Configuration + static class NoOpFirewallConfig { + + @Bean + ServerWebExchangeFirewall noOpFirewall() { + return ServerWebExchangeFirewall.INSECURE_NOOP; + } + + } + @Configuration static class SubclassConfig extends WebFluxSecurityConfiguration {