Skip to content

Add ContinueOnError Support for Failed Authentications #14591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

Expand All @@ -27,8 +30,9 @@

/**
* A {@link ReactiveAuthenticationManager} that delegates to other
* {@link ReactiveAuthenticationManager} instances using the result from the first non
* empty result.
* {@link ReactiveAuthenticationManager} instances. When {@code continueOnError} is
* {@code true}, will continue until the first non-empty, non-error result; otherwise,
* will continue only until the first non-empty result.
*
* @author Rob Winch
* @since 5.1
Expand All @@ -37,6 +41,10 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti

private final List<ReactiveAuthenticationManager> delegates;

private boolean continueOnError = false;

private final Log logger = LogFactory.getLog(getClass());

public DelegatingReactiveAuthenticationManager(ReactiveAuthenticationManager... entryPoints) {
this(Arrays.asList(entryPoints));
}
Expand All @@ -48,11 +56,20 @@ public DelegatingReactiveAuthenticationManager(List<ReactiveAuthenticationManage

@Override
public Mono<Authentication> authenticate(Authentication authentication) {
// @formatter:off
return Flux.fromIterable(this.delegates)
.concatMap((m) -> m.authenticate(authentication))
.next();
// @formatter:on
Flux<ReactiveAuthenticationManager> result = Flux.fromIterable(this.delegates);
Function<ReactiveAuthenticationManager, Mono<Authentication>> logging = (m) -> m.authenticate(authentication)
.doOnError(this.logger::debug);

return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next();
}

/**
* Continue iterating when a delegate errors, defaults to {@code false}
* @param continueOnError whether to continue when a delegate errors
* @since 6.3
*/
public void setContinueOnError(boolean continueOnError) {
this.continueOnError = continueOnError;
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
}

}