Skip to content

Commit 2cabbd8

Browse files
ruabtmhruabtmh
ruabtmh
authored and
ruabtmh
committed
Add ContinueOnError Support For Failed Authentications
Closes gh-14521
1 parent e771267 commit 2cabbd8

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,10 @@
1818

1919
import java.util.Arrays;
2020
import java.util.List;
21+
import java.util.function.Function;
2122

23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
2225
import reactor.core.publisher.Flux;
2326
import reactor.core.publisher.Mono;
2427

@@ -37,6 +40,10 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti
3740

3841
private final List<ReactiveAuthenticationManager> delegates;
3942

43+
private boolean continueOnError = false;
44+
45+
private final Log logger = LogFactory.getLog(getClass());
46+
4047
public DelegatingReactiveAuthenticationManager(ReactiveAuthenticationManager... entryPoints) {
4148
this(Arrays.asList(entryPoints));
4249
}
@@ -48,11 +55,16 @@ public DelegatingReactiveAuthenticationManager(List<ReactiveAuthenticationManage
4855

4956
@Override
5057
public Mono<Authentication> authenticate(Authentication authentication) {
51-
// @formatter:off
52-
return Flux.fromIterable(this.delegates)
53-
.concatMap((m) -> m.authenticate(authentication))
54-
.next();
55-
// @formatter:on
58+
Flux<ReactiveAuthenticationManager> result = Flux.fromIterable(this.delegates);
59+
Function<ReactiveAuthenticationManager, Mono<Authentication>> logging =
60+
(m) -> m.authenticate(authentication).doOnError(logger::debug);
61+
62+
return ((this.continueOnError) ? result.concatMapDelayError(logging)
63+
: result.concatMap(logging)).next();
64+
}
65+
66+
public void setContinueOnError(boolean continueOnError) {
67+
this.continueOnError = continueOnError;
5668
}
5769

5870
}

core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ public void authenticateWhenNotEmptyThenOtherDelegatesNotSubscribed() {
6161
// delay to try and force delegate2 to finish (i.e. make sure we didn't use
6262
// flatMap)
6363
given(this.delegate1.authenticate(any()))
64-
.willReturn(Mono.just(this.authentication).delayElement(Duration.ofMillis(100)));
64+
.willReturn(Mono.just(this.authentication).delayElement(Duration.ofMillis(100)));
6565
DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1,
6666
this.delegate2);
6767
StepVerifier.create(manager.authenticate(this.authentication)).expectNext(this.authentication).verifyComplete();
@@ -73,8 +73,46 @@ public void authenticateWhenBadCredentialsThenDelegate2NotInvokedAndError() {
7373
DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1,
7474
this.delegate2);
7575
StepVerifier.create(manager.authenticate(this.authentication))
76-
.expectError(BadCredentialsException.class)
77-
.verify();
76+
.expectError(BadCredentialsException.class)
77+
.verify();
7878
}
7979

80+
@Test
81+
public void authenticateWhenContinueOnErrorAndFirstBadCredentialsThenTriesSecond() {
82+
given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test")));
83+
given(this.delegate2.authenticate(any())).willReturn(Mono.just(this.authentication));
84+
85+
DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError();
86+
87+
assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication);
88+
}
89+
90+
@Test
91+
public void authenticateWhenContinueOnErrorAndBothDelegatesBadCredentialsThenError() {
92+
given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test")));
93+
given(this.delegate2.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test")));
94+
95+
DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError();
96+
97+
StepVerifier.create(manager.authenticate(this.authentication))
98+
.expectError(BadCredentialsException.class)
99+
.verify();
100+
}
101+
102+
@Test
103+
public void authenticateWhenContinueOnErrorAndDelegate1NotEmptyThenReturnsNotEmpty() {
104+
given(this.delegate1.authenticate(any())).willReturn(Mono.just(this.authentication));
105+
106+
DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError();
107+
108+
assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication);
109+
}
110+
111+
private DelegatingReactiveAuthenticationManager managerWithContinueOnError() {
112+
DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1,
113+
this.delegate2);
114+
manager.setContinueOnError(true);
115+
116+
return manager;
117+
}
80118
}

0 commit comments

Comments
 (0)