From 9e2f34b055ba61947b342d47513bc46a40a23978 Mon Sep 17 00:00:00 2001 From: ruabtmh Date: Tue, 13 Feb 2024 11:47:23 +0300 Subject: [PATCH] Add ContinueOnError Support For Failed Authentications Closes gh-14521 --- ...legatingReactiveAuthenticationManager.java | 25 +++++++---- ...ingReactiveAuthenticationManagerTests.java | 41 ++++++++++++++++++- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java b/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java index 7a06a0695b5..5893100f997 100644 --- a/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java +++ b/core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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,7 +18,10 @@ import java.util.Arrays; import java.util.List; +import java.util.function.Function; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,7 +31,7 @@ /** * A {@link ReactiveAuthenticationManager} that delegates to other * {@link ReactiveAuthenticationManager} instances using the result from the first non - * empty result. + * empty result. Errors from delegates will be ignored if continueOnError is true. * * @author Rob Winch * @since 5.1 @@ -37,6 +40,10 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti private final List delegates; + private boolean continueOnError = false; + + private final Log logger = LogFactory.getLog(getClass()); + public DelegatingReactiveAuthenticationManager(ReactiveAuthenticationManager... entryPoints) { this(Arrays.asList(entryPoints)); } @@ -48,11 +55,15 @@ public DelegatingReactiveAuthenticationManager(List authenticate(Authentication authentication) { - // @formatter:off - return Flux.fromIterable(this.delegates) - .concatMap((m) -> m.authenticate(authentication)) - .next(); - // @formatter:on + Flux result = Flux.fromIterable(this.delegates); + Function> logging = (m) -> m.authenticate(authentication) + .doOnError(logger::debug); + + return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next(); + } + + public void setContinueOnError(boolean continueOnError) { + this.continueOnError = continueOnError; } } diff --git a/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java b/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java index dd89bd7c89e..2d4b2c7a158 100644 --- a/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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. @@ -77,4 +77,43 @@ public void authenticateWhenBadCredentialsThenDelegate2NotInvokedAndError() { .verify(); } + @Test + public void authenticateWhenContinueOnErrorAndFirstBadCredentialsThenTriesSecond() { + given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test"))); + given(this.delegate2.authenticate(any())).willReturn(Mono.just(this.authentication)); + + DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError(); + + assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication); + } + + @Test + public void authenticateWhenContinueOnErrorAndBothDelegatesBadCredentialsThenError() { + given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test"))); + given(this.delegate2.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test"))); + + DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError(); + + StepVerifier.create(manager.authenticate(this.authentication)) + .expectError(BadCredentialsException.class) + .verify(); + } + + @Test + public void authenticateWhenContinueOnErrorAndDelegate1NotEmptyThenReturnsNotEmpty() { + given(this.delegate1.authenticate(any())).willReturn(Mono.just(this.authentication)); + + DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError(); + + assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication); + } + + private DelegatingReactiveAuthenticationManager managerWithContinueOnError() { + DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1, + this.delegate2); + manager.setContinueOnError(true); + + return manager; + } + }