diff --git a/webflux/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java b/webflux/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java index 7321e2951f..c2b6a945d3 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java +++ b/webflux/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java @@ -17,9 +17,13 @@ */ package org.springframework.security.web.server.authentication; +import java.util.function.Function; + +import org.springframework.security.core.AuthenticationException; +import reactor.core.publisher.Mono; + import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.web.server.AuthenticationEntryPoint; import org.springframework.security.web.server.HttpBasicAuthenticationConverter; @@ -27,13 +31,12 @@ import org.springframework.security.web.server.authentication.www.HttpBasicAuthe import org.springframework.security.web.server.context.SecurityContextRepository; import org.springframework.security.web.server.context.SecurityContextRepositoryServerWebExchange; import org.springframework.security.web.server.context.ServerWebExchangeAttributeSecurityContextRepository; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -import java.util.function.Function; /** * @@ -52,6 +55,8 @@ public class AuthenticationWebFilter implements WebFilter { private SecurityContextRepository securityContextRepository = new ServerWebExchangeAttributeSecurityContextRepository(); + private ServerWebExchangeMatcher requiresAuthenticationMatcher = ServerWebExchangeMatchers.anyExchange(); + public AuthenticationWebFilter(ReactiveAuthenticationManager authenticationManager) { Assert.notNull(authenticationManager, "authenticationManager cannot be null"); this.authenticationManager = authenticationManager; @@ -59,17 +64,23 @@ public class AuthenticationWebFilter implements WebFilter { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - ServerWebExchange wrappedExchange = wrap(exchange); - return this.authenticationConverter.apply(wrappedExchange) - .switchIfEmpty(Mono.defer(() -> chain.filter(wrappedExchange).cast(Authentication.class))) - .flatMap( token -> this.authenticationManager.authenticate(token) - .flatMap(authentication -> onAuthenticationSuccess(authentication, wrappedExchange, chain)) - .onErrorResume( AuthenticationException.class, t -> this.entryPoint.commence(wrappedExchange, t)) - ); + ServerWebExchange wrappedExchange = new SecurityContextRepositoryServerWebExchange(exchange, this.securityContextRepository); + return filterInternal(wrappedExchange, chain); } - private ServerWebExchange wrap(ServerWebExchange exchange) { - return new SecurityContextRepositoryServerWebExchange(exchange, this.securityContextRepository); + private Mono filterInternal(ServerWebExchange wrappedExchange, WebFilterChain chain) { + return this.requiresAuthenticationMatcher.matches(wrappedExchange) + .filter( matchResult -> matchResult.isMatch()) + .flatMap( matchResult -> this.authenticationConverter.apply(wrappedExchange)) + .switchIfEmpty(chain.filter(wrappedExchange).then(Mono.empty())) + .flatMap( token -> authenticate(wrappedExchange, chain, token)); + } + + private Mono authenticate(ServerWebExchange wrappedExchange, + WebFilterChain chain, Authentication token) { + return this.authenticationManager.authenticate(token) + .flatMap(authentication -> onAuthenticationSuccess(authentication, wrappedExchange, chain)) + .onErrorResume(AuthenticationException.class, e -> this.entryPoint.commence(wrappedExchange, e)); } private Mono onAuthenticationSuccess(Authentication authentication, ServerWebExchange exchange, WebFilterChain chain) { @@ -96,4 +107,10 @@ public class AuthenticationWebFilter implements WebFilter { public void setEntryPoint(AuthenticationEntryPoint entryPoint) { this.entryPoint = entryPoint; } + + public void setRequiresAuthenticationMatcher( + ServerWebExchangeMatcher requiresAuthenticationMatcher) { + Assert.notNull(requiresAuthenticationMatcher, "requiresAuthenticationMatcher cannot be null"); + this.requiresAuthenticationMatcher = requiresAuthenticationMatcher; + } } diff --git a/webflux/src/test/java/org/springframework/security/web/server/authentication/AuthenticationWebFilterTests.java b/webflux/src/test/java/org/springframework/security/web/server/authentication/AuthenticationWebFilterTests.java index 7093879543..2abfd9659a 100644 --- a/webflux/src/test/java/org/springframework/security/web/server/authentication/AuthenticationWebFilterTests.java +++ b/webflux/src/test/java/org/springframework/security/web/server/authentication/AuthenticationWebFilterTests.java @@ -34,6 +34,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.AuthenticationEntryPoint; import org.springframework.security.web.server.context.SecurityContextRepository; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.test.web.reactive.server.EntityExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.server.ServerWebExchange; @@ -206,6 +207,29 @@ public class AuthenticationWebFilterTests { verifyZeroInteractions(this.entryPoint); } + @Test + public void filterWhenNotMatchAndConvertAndAuthenticationSuccessThenContinues() { + this.filter.setRequiresAuthenticationMatcher(e -> ServerWebExchangeMatcher.MatchResult.notMatch()); + + WebTestClient client = WebTestClientBuilder + .bindToWebFilters(this.filter) + .build(); + + EntityExchangeResult result = client + .mutate() + .filter(basicAuthentication("test","this")) + .build() + .get() + .uri("/") + .exchange() + .expectStatus().isOk() + .expectBody(String.class).consumeWith(b -> assertThat(b.getResponseBody()).isEqualTo("ok")) + .returnResult(); + + assertThat(result.getResponseCookies()).isEmpty(); + verifyZeroInteractions(this.authenticationConverter, this.authenticationManager, this.successHandler); + } + @Test public void filterWhenConvertAndAuthenticationFailThenEntryPoint() { Mono authentication = Mono.just(new TestingAuthenticationToken("test", "this", "ROLE_USER")); @@ -250,4 +274,9 @@ public class AuthenticationWebFilterTests { verify(this.securityContextRepository, never()).save(any(), any()); verifyZeroInteractions(this.successHandler, this.entryPoint); } + + @Test(expected = IllegalArgumentException.class) + public void setRequiresAuthenticationMatcherWhenNullThenException() { + this.filter.setRequiresAuthenticationMatcher(null); + } }