DaoAuthenticationProvider supports password upgrades
Issue: gh-2778
This commit is contained in:
parent
cabd0a5579
commit
7aaf70d582
|
@ -20,12 +20,14 @@ import org.springframework.security.authentication.AuthenticationProvider;
|
|||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -62,6 +64,8 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
|
|||
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
private UserDetailsPasswordService userDetailsPasswordService;
|
||||
|
||||
public DaoAuthenticationProvider() {
|
||||
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
|
||||
}
|
||||
|
@ -120,6 +124,19 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication createSuccessAuthentication(Object principal,
|
||||
Authentication authentication, UserDetails user) {
|
||||
boolean upgradeEncoding = this.userDetailsPasswordService != null
|
||||
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
|
||||
if (upgradeEncoding) {
|
||||
String presentedPassword = authentication.getCredentials().toString();
|
||||
String newPassword = this.passwordEncoder.encode(presentedPassword);
|
||||
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
|
||||
}
|
||||
return super.createSuccessAuthentication(principal, authentication, user);
|
||||
}
|
||||
|
||||
private void prepareTimingAttackProtection() {
|
||||
if (this.userNotFoundEncodedPassword == null) {
|
||||
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
|
||||
|
@ -157,4 +174,9 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
|
|||
protected UserDetailsService getUserDetailsService() {
|
||||
return userDetailsService;
|
||||
}
|
||||
|
||||
public void setUserDetailsPasswordService(
|
||||
UserDetailsPasswordService userDetailsPasswordService) {
|
||||
this.userDetailsPasswordService = userDetailsPasswordService;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,16 @@
|
|||
package org.springframework.security.authentication.dao;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
@ -43,6 +47,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
@ -53,6 +58,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||||
|
||||
/**
|
||||
* Tests {@link DaoAuthenticationProvider}.
|
||||
|
@ -399,6 +405,80 @@ public class DaoAuthenticationProviderTests {
|
|||
assertThat(castResult.getPrincipal()).isEqualTo("rod");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenSuccessAndPasswordManagerThenUpdates() {
|
||||
String password = "password";
|
||||
String encodedPassword = "encoded";
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
"user", password);
|
||||
|
||||
PasswordEncoder encoder = mock(PasswordEncoder.class);
|
||||
UserDetailsService userDetailsService = mock(UserDetailsService.class);
|
||||
UserDetailsPasswordService passwordManager = mock(UserDetailsPasswordService.class);
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setPasswordEncoder(encoder);
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
provider.setUserDetailsPasswordService(passwordManager);
|
||||
|
||||
UserDetails user = PasswordEncodedUser.user();
|
||||
when(encoder.matches(any(), any())).thenReturn(true);
|
||||
when(encoder.upgradeEncoding(any())).thenReturn(true);
|
||||
when(encoder.encode(any())).thenReturn(encodedPassword);
|
||||
when(userDetailsService.loadUserByUsername(any())).thenReturn(user);
|
||||
when(passwordManager.updatePassword(any(), any())).thenReturn(user);
|
||||
|
||||
Authentication result = provider.authenticate(token);
|
||||
|
||||
verify(encoder).encode(password);
|
||||
verify(passwordManager).updatePassword(eq(user), eq(encodedPassword));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenBadCredentialsAndPasswordManagerThenNoUpdate() {
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
"user", "password");
|
||||
|
||||
PasswordEncoder encoder = mock(PasswordEncoder.class);
|
||||
UserDetailsService userDetailsService = mock(UserDetailsService.class);
|
||||
UserDetailsPasswordService passwordManager = mock(UserDetailsPasswordService.class);
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setPasswordEncoder(encoder);
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
provider.setUserDetailsPasswordService(passwordManager);
|
||||
|
||||
UserDetails user = PasswordEncodedUser.user();
|
||||
when(encoder.matches(any(), any())).thenReturn(false);
|
||||
when(userDetailsService.loadUserByUsername(any())).thenReturn(user);
|
||||
|
||||
assertThatThrownBy(() -> provider.authenticate(token))
|
||||
.isInstanceOf(BadCredentialsException.class);
|
||||
|
||||
verifyZeroInteractions(passwordManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenNotUpgradeAndPasswordManagerThenNoUpdate() {
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
"user", "password");
|
||||
|
||||
PasswordEncoder encoder = mock(PasswordEncoder.class);
|
||||
UserDetailsService userDetailsService = mock(UserDetailsService.class);
|
||||
UserDetailsPasswordService passwordManager = mock(UserDetailsPasswordService.class);
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setPasswordEncoder(encoder);
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
provider.setUserDetailsPasswordService(passwordManager);
|
||||
|
||||
UserDetails user = PasswordEncodedUser.user();
|
||||
when(encoder.matches(any(), any())).thenReturn(true);
|
||||
when(encoder.upgradeEncoding(any())).thenReturn(false);
|
||||
when(userDetailsService.loadUserByUsername(any())).thenReturn(user);
|
||||
|
||||
Authentication result = provider.authenticate(token);
|
||||
|
||||
verifyZeroInteractions(passwordManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectsNullBeingReturnedFromAuthenticationDao() {
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
|
|
Loading…
Reference in New Issue