Skip to content

Commit 4178c92

Browse files
edeandrearwinch
authored andcommitted
Add Reactive Support for UserDetailsChecker
Integrate UserDetailsChecker into ReactiveAuthenticationManager and OAuth2 resource server authentication converters. Fixes gh-6219
1 parent 56eb658 commit 4178c92

File tree

2 files changed

+61
-14
lines changed

2 files changed

+61
-14
lines changed

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

+20-4
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@
1616

1717
package org.springframework.security.authentication;
1818

19-
import org.springframework.security.core.Authentication;
19+
import reactor.core.publisher.Mono;
20+
import reactor.core.scheduler.Scheduler;
21+
import reactor.core.scheduler.Schedulers;
2022

23+
import org.springframework.security.core.Authentication;
2124
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
2225
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
26+
import org.springframework.security.core.userdetails.UserDetailsChecker;
2327
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
2428
import org.springframework.security.crypto.password.PasswordEncoder;
2529
import org.springframework.util.Assert;
26-
import reactor.core.publisher.Mono;
27-
import reactor.core.scheduler.Scheduler;
28-
import reactor.core.scheduler.Schedulers;
2930

3031
/**
3132
* A {@link ReactiveAuthenticationManager} that uses a {@link ReactiveUserDetailsService} to validate the provided
@@ -43,6 +44,8 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
4344

4445
private Scheduler scheduler = Schedulers.parallel();
4546

47+
private UserDetailsChecker postAuthenticationChecks = userDetails -> {};
48+
4649
public UserDetailsRepositoryReactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService) {
4750
Assert.notNull(userDetailsService, "userDetailsService cannot be null");
4851
this.userDetailsService = userDetailsService;
@@ -65,6 +68,7 @@ public Mono<Authentication> authenticate(Authentication authentication) {
6568
}
6669
return Mono.just(u);
6770
})
71+
.doOnNext(this.postAuthenticationChecks::check)
6872
.map(u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()) );
6973
}
7074

@@ -102,4 +106,16 @@ public void setUserDetailsPasswordService(
102106
ReactiveUserDetailsPasswordService userDetailsPasswordService) {
103107
this.userDetailsPasswordService = userDetailsPasswordService;
104108
}
109+
110+
/**
111+
* Sets the strategy which will be used to validate the loaded <tt>UserDetails</tt>
112+
* object after authentication occurs.
113+
*
114+
* @param postAuthenticationChecks The {@link UserDetailsChecker}
115+
* @since 5.2
116+
*/
117+
public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
118+
Assert.notNull(this.postAuthenticationChecks, "postAuthenticationChecks cannot be null");
119+
this.postAuthenticationChecks = postAuthenticationChecks;
120+
}
105121
}

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

+41-10
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,26 @@
1616

1717
package org.springframework.security.authentication;
1818

19+
import static org.assertj.core.api.Assertions.*;
20+
import static org.mockito.Mockito.*;
21+
1922
import org.junit.Before;
2023
import org.junit.Test;
2124
import org.junit.runner.RunWith;
2225
import org.mockito.Mock;
2326
import org.mockito.junit.MockitoJUnitRunner;
27+
28+
import reactor.core.publisher.Mono;
29+
import reactor.core.scheduler.Scheduler;
30+
import reactor.core.scheduler.Schedulers;
31+
2432
import org.springframework.security.core.Authentication;
2533
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
2634
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
2735
import org.springframework.security.core.userdetails.User;
2836
import org.springframework.security.core.userdetails.UserDetails;
37+
import org.springframework.security.core.userdetails.UserDetailsChecker;
2938
import org.springframework.security.crypto.password.PasswordEncoder;
30-
import reactor.core.publisher.Mono;
31-
import reactor.core.scheduler.Scheduler;
32-
import reactor.core.scheduler.Schedulers;
33-
34-
import static org.assertj.core.api.Assertions.*;
35-
import static org.mockito.ArgumentMatchers.any;
36-
import static org.mockito.ArgumentMatchers.eq;
37-
import static org.mockito.Mockito.verify;
38-
import static org.mockito.Mockito.verifyZeroInteractions;
39-
import static org.mockito.Mockito.when;
4039

4140
/**
4241
* @author Rob Winch
@@ -56,6 +55,9 @@ public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
5655
@Mock
5756
private Scheduler scheduler;
5857

58+
@Mock
59+
private UserDetailsChecker postAuthenticationChecks;
60+
5961
private UserDetails user = User.withUsername("user")
6062
.password("password")
6163
.roles("USER")
@@ -140,4 +142,33 @@ public void authenticateWhenPasswordServiceAndUpgradeFalseThenNotUpdated() {
140142

141143
verifyZeroInteractions(this.userDetailsPasswordService);
142144
}
145+
146+
@Test
147+
public void authenticateWhenPostAuthenticationChecksFail() {
148+
when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
149+
doThrow(new LockedException("account is locked")).when(this.postAuthenticationChecks).check(any());
150+
when(this.encoder.matches(any(), any())).thenReturn(true);
151+
this.manager.setPasswordEncoder(this.encoder);
152+
this.manager.setPostAuthenticationChecks(this.postAuthenticationChecks);
153+
154+
assertThatExceptionOfType(LockedException.class)
155+
.isThrownBy(() -> this.manager.authenticate(new UsernamePasswordAuthenticationToken(this.user, this.user.getPassword())).block())
156+
.withMessage("account is locked");
157+
158+
verify(this.postAuthenticationChecks).check(eq(this.user));
159+
}
160+
161+
@Test
162+
public void authenticateWhenPostAuthenticationChecksNotSet() {
163+
when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
164+
when(this.encoder.matches(any(), any())).thenReturn(true);
165+
this.manager.setPasswordEncoder(this.encoder);
166+
167+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
168+
this.user, this.user.getPassword());
169+
170+
this.manager.authenticate(token).block();
171+
172+
verifyZeroInteractions(this.postAuthenticationChecks);
173+
}
143174
}

0 commit comments

Comments
 (0)