Skip to content

Commit 7aaf70d

Browse files
committed
DaoAuthenticationProvider supports password upgrades
Issue: gh-2778
1 parent cabd0a5 commit 7aaf70d

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java

+22
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import org.springframework.security.authentication.BadCredentialsException;
2121
import org.springframework.security.authentication.InternalAuthenticationServiceException;
2222
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
23+
import org.springframework.security.core.Authentication;
2324
import org.springframework.security.core.AuthenticationException;
2425
import org.springframework.security.core.userdetails.UserDetails;
2526
import org.springframework.security.core.userdetails.UserDetailsService;
2627
import org.springframework.security.core.userdetails.UsernameNotFoundException;
2728
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
2829
import org.springframework.security.crypto.password.PasswordEncoder;
30+
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
2931
import org.springframework.util.Assert;
3032

3133
/**
@@ -62,6 +64,8 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
6264

6365
private UserDetailsService userDetailsService;
6466

67+
private UserDetailsPasswordService userDetailsPasswordService;
68+
6569
public DaoAuthenticationProvider() {
6670
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
6771
}
@@ -120,6 +124,19 @@ protected final UserDetails retrieveUser(String username,
120124
}
121125
}
122126

127+
@Override
128+
protected Authentication createSuccessAuthentication(Object principal,
129+
Authentication authentication, UserDetails user) {
130+
boolean upgradeEncoding = this.userDetailsPasswordService != null
131+
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
132+
if (upgradeEncoding) {
133+
String presentedPassword = authentication.getCredentials().toString();
134+
String newPassword = this.passwordEncoder.encode(presentedPassword);
135+
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
136+
}
137+
return super.createSuccessAuthentication(principal, authentication, user);
138+
}
139+
123140
private void prepareTimingAttackProtection() {
124141
if (this.userNotFoundEncodedPassword == null) {
125142
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
@@ -157,4 +174,9 @@ public void setUserDetailsService(UserDetailsService userDetailsService) {
157174
protected UserDetailsService getUserDetailsService() {
158175
return userDetailsService;
159176
}
177+
178+
public void setUserDetailsPasswordService(
179+
UserDetailsPasswordService userDetailsPasswordService) {
180+
this.userDetailsPasswordService = userDetailsPasswordService;
181+
}
160182
}

core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java

+80
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
package org.springframework.security.authentication.dao;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2021
import static org.assertj.core.api.Assertions.fail;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.ArgumentMatchers.eq;
2124
import static org.mockito.Matchers.anyString;
2225
import static org.mockito.Matchers.isA;
2326
import static org.mockito.Mockito.mock;
2427
import static org.mockito.Mockito.times;
2528
import static org.mockito.Mockito.verify;
29+
import static org.mockito.Mockito.verifyZeroInteractions;
2630
import static org.mockito.Mockito.when;
2731

2832
import java.security.SecureRandom;
@@ -43,6 +47,7 @@
4347
import org.springframework.security.core.Authentication;
4448
import org.springframework.security.core.GrantedAuthority;
4549
import org.springframework.security.core.authority.AuthorityUtils;
50+
import org.springframework.security.core.userdetails.PasswordEncodedUser;
4651
import org.springframework.security.core.userdetails.User;
4752
import org.springframework.security.core.userdetails.UserDetails;
4853
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -53,6 +58,7 @@
5358
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
5459
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
5560
import org.springframework.security.crypto.password.PasswordEncoder;
61+
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
5662

5763
/**
5864
* Tests {@link DaoAuthenticationProvider}.
@@ -399,6 +405,80 @@ public void testAuthenticatesWithForcePrincipalAsString() {
399405
assertThat(castResult.getPrincipal()).isEqualTo("rod");
400406
}
401407

408+
@Test
409+
public void authenticateWhenSuccessAndPasswordManagerThenUpdates() {
410+
String password = "password";
411+
String encodedPassword = "encoded";
412+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
413+
"user", password);
414+
415+
PasswordEncoder encoder = mock(PasswordEncoder.class);
416+
UserDetailsService userDetailsService = mock(UserDetailsService.class);
417+
UserDetailsPasswordService passwordManager = mock(UserDetailsPasswordService.class);
418+
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
419+
provider.setPasswordEncoder(encoder);
420+
provider.setUserDetailsService(userDetailsService);
421+
provider.setUserDetailsPasswordService(passwordManager);
422+
423+
UserDetails user = PasswordEncodedUser.user();
424+
when(encoder.matches(any(), any())).thenReturn(true);
425+
when(encoder.upgradeEncoding(any())).thenReturn(true);
426+
when(encoder.encode(any())).thenReturn(encodedPassword);
427+
when(userDetailsService.loadUserByUsername(any())).thenReturn(user);
428+
when(passwordManager.updatePassword(any(), any())).thenReturn(user);
429+
430+
Authentication result = provider.authenticate(token);
431+
432+
verify(encoder).encode(password);
433+
verify(passwordManager).updatePassword(eq(user), eq(encodedPassword));
434+
}
435+
436+
@Test
437+
public void authenticateWhenBadCredentialsAndPasswordManagerThenNoUpdate() {
438+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
439+
"user", "password");
440+
441+
PasswordEncoder encoder = mock(PasswordEncoder.class);
442+
UserDetailsService userDetailsService = mock(UserDetailsService.class);
443+
UserDetailsPasswordService passwordManager = mock(UserDetailsPasswordService.class);
444+
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
445+
provider.setPasswordEncoder(encoder);
446+
provider.setUserDetailsService(userDetailsService);
447+
provider.setUserDetailsPasswordService(passwordManager);
448+
449+
UserDetails user = PasswordEncodedUser.user();
450+
when(encoder.matches(any(), any())).thenReturn(false);
451+
when(userDetailsService.loadUserByUsername(any())).thenReturn(user);
452+
453+
assertThatThrownBy(() -> provider.authenticate(token))
454+
.isInstanceOf(BadCredentialsException.class);
455+
456+
verifyZeroInteractions(passwordManager);
457+
}
458+
459+
@Test
460+
public void authenticateWhenNotUpgradeAndPasswordManagerThenNoUpdate() {
461+
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
462+
"user", "password");
463+
464+
PasswordEncoder encoder = mock(PasswordEncoder.class);
465+
UserDetailsService userDetailsService = mock(UserDetailsService.class);
466+
UserDetailsPasswordService passwordManager = mock(UserDetailsPasswordService.class);
467+
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
468+
provider.setPasswordEncoder(encoder);
469+
provider.setUserDetailsService(userDetailsService);
470+
provider.setUserDetailsPasswordService(passwordManager);
471+
472+
UserDetails user = PasswordEncodedUser.user();
473+
when(encoder.matches(any(), any())).thenReturn(true);
474+
when(encoder.upgradeEncoding(any())).thenReturn(false);
475+
when(userDetailsService.loadUserByUsername(any())).thenReturn(user);
476+
477+
Authentication result = provider.authenticate(token);
478+
479+
verifyZeroInteractions(passwordManager);
480+
}
481+
402482
@Test
403483
public void testDetectsNullBeingReturnedFromAuthenticationDao() {
404484
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(

0 commit comments

Comments
 (0)