Skip to content

Commit 72a267a

Browse files
committed
UserDetailsRepositoryReactiveAuthenticationManager uses ReactiveUserDetailsPasswordService
Issue: gh-2778
1 parent ed8218a commit 72a267a

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

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

+25-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import org.springframework.security.core.Authentication;
2020

21+
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
2122
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
23+
import org.springframework.security.core.userdetails.User;
2224
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
2325
import org.springframework.security.crypto.password.PasswordEncoder;
2426
import org.springframework.util.Assert;
@@ -38,6 +40,8 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
3840

3941
private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
4042

43+
private ReactiveUserDetailsPasswordService userDetailsPasswordService;
44+
4145
private Scheduler scheduler = Schedulers.parallel();
4246

4347
public UserDetailsRepositoryReactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService) {
@@ -48,11 +52,21 @@ public UserDetailsRepositoryReactiveAuthenticationManager(ReactiveUserDetailsSer
4852
@Override
4953
public Mono<Authentication> authenticate(Authentication authentication) {
5054
final String username = authentication.getName();
55+
final String presentedPassword = (String) authentication.getCredentials();
5156
return this.userDetailsService.findByUsername(username)
5257
.publishOn(this.scheduler)
53-
.filter( u -> this.passwordEncoder.matches((String) authentication.getCredentials(), u.getPassword()))
58+
.filter(u -> this.passwordEncoder.matches(presentedPassword, u.getPassword()))
5459
.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
55-
.map( u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()) );
60+
.flatMap(u -> {
61+
boolean upgradeEncoding = this.userDetailsPasswordService != null
62+
&& this.passwordEncoder.upgradeEncoding(u.getPassword());
63+
if (upgradeEncoding) {
64+
String newPassword = this.passwordEncoder.encode(presentedPassword);
65+
return this.userDetailsPasswordService.updatePassword(u, newPassword);
66+
}
67+
return Mono.just(u);
68+
})
69+
.map(u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()) );
5670
}
5771

5872
/**
@@ -80,4 +94,13 @@ public void setScheduler(Scheduler scheduler) {
8094
Assert.notNull(scheduler, "scheduler cannot be null");
8195
this.scheduler = scheduler;
8296
}
97+
98+
/**
99+
* Sets the service to use for upgrading passwords on successful authentication.
100+
* @param userDetailsPasswordService the service to use
101+
*/
102+
public void setUserDetailsPasswordService(
103+
ReactiveUserDetailsPasswordService userDetailsPasswordService) {
104+
this.userDetailsPasswordService = userDetailsPasswordService;
105+
}
83106
}

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

+58-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.mockito.Mock;
2323
import org.mockito.junit.MockitoJUnitRunner;
2424
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
2526
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
2627
import org.springframework.security.core.userdetails.User;
2728
import org.springframework.security.core.userdetails.UserDetails;
@@ -32,7 +33,9 @@
3233

3334
import static org.assertj.core.api.Assertions.*;
3435
import static org.mockito.ArgumentMatchers.any;
36+
import static org.mockito.ArgumentMatchers.eq;
3537
import static org.mockito.Mockito.verify;
38+
import static org.mockito.Mockito.verifyZeroInteractions;
3639
import static org.mockito.Mockito.when;
3740

3841
/**
@@ -47,6 +50,9 @@ public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
4750
@Mock
4851
private PasswordEncoder encoder;
4952

53+
@Mock
54+
private ReactiveUserDetailsPasswordService userDetailsPasswordService;
55+
5056
@Mock
5157
private Scheduler scheduler;
5258

@@ -79,10 +85,61 @@ public void authentiateWhenCustomSchedulerThenUsed() {
7985
this.manager.setScheduler(this.scheduler);
8086
this.manager.setPasswordEncoder(this.encoder);
8187
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
82-
this.user, this.user.getPassword());
88+
this.user, this.user.getPassword());
8389

8490
Authentication result = this.manager.authenticate(token).block();
8591

8692
verify(this.scheduler).schedule(any());
8793
}
94+
95+
@Test
96+
public void authenticateWhenPasswordServiceThenUpdated() {
97+
String encodedPassword = "encoded";
98+
when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
99+
when(this.encoder.matches(any(), any())).thenReturn(true);
100+
when(this.encoder.upgradeEncoding(any())).thenReturn(true);
101+
when(this.encoder.encode(any())).thenReturn(encodedPassword);
102+
when(this.userDetailsPasswordService.updatePassword(any(), any())).thenReturn(Mono.just(this.user));
103+
this.manager.setPasswordEncoder(this.encoder);
104+
this.manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
105+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
106+
this.user, this.user.getPassword());
107+
108+
Authentication result = this.manager.authenticate(token).block();
109+
110+
verify(this.encoder).encode(this.user.getPassword());
111+
verify(this.userDetailsPasswordService).updatePassword(eq(this.user), eq(encodedPassword));
112+
}
113+
114+
@Test
115+
public void authenticateWhenPasswordServiceAndBadCredentialsThenNotUpdated() {
116+
when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
117+
when(this.encoder.matches(any(), any())).thenReturn(false);
118+
when(this.userDetailsPasswordService.updatePassword(any(), any())).thenReturn(Mono.just(this.user));
119+
this.manager.setPasswordEncoder(this.encoder);
120+
this.manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
121+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
122+
this.user, this.user.getPassword());
123+
124+
assertThatThrownBy(() -> this.manager.authenticate(token).block())
125+
.isInstanceOf(BadCredentialsException.class);
126+
127+
verifyZeroInteractions(this.userDetailsPasswordService);
128+
}
129+
130+
@Test
131+
public void authenticateWhenPasswordServiceAndUpgradeFalseThenNotUpdated() {
132+
when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
133+
when(this.encoder.matches(any(), any())).thenReturn(true);
134+
when(this.encoder.upgradeEncoding(any())).thenReturn(false);
135+
when(this.userDetailsPasswordService.updatePassword(any(), any())).thenReturn(Mono.just(this.user));
136+
this.manager.setPasswordEncoder(this.encoder);
137+
this.manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
138+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
139+
this.user, this.user.getPassword());
140+
141+
Authentication result = this.manager.authenticate(token).block();
142+
143+
verifyZeroInteractions(this.userDetailsPasswordService);
144+
}
88145
}

0 commit comments

Comments
 (0)