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.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,6 +64,8 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
|
||||||
|
|
||||||
private UserDetailsService userDetailsService;
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
private UserDetailsPasswordService userDetailsPasswordService;
|
||||||
|
|
||||||
public DaoAuthenticationProvider() {
|
public DaoAuthenticationProvider() {
|
||||||
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
|
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() {
|
private void prepareTimingAttackProtection() {
|
||||||
if (this.userNotFoundEncodedPassword == null) {
|
if (this.userNotFoundEncodedPassword == null) {
|
||||||
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
|
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
|
||||||
|
@ -157,4 +174,9 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
|
||||||
protected UserDetailsService getUserDetailsService() {
|
protected UserDetailsService getUserDetailsService() {
|
||||||
return userDetailsService;
|
return userDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserDetailsPasswordService(
|
||||||
|
UserDetailsPasswordService userDetailsPasswordService) {
|
||||||
|
this.userDetailsPasswordService = userDetailsPasswordService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,16 @@
|
||||||
package org.springframework.security.authentication.dao;
|
package org.springframework.security.authentication.dao;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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.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.anyString;
|
||||||
import static org.mockito.Matchers.isA;
|
import static org.mockito.Matchers.isA;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
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.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
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.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
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.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DaoAuthenticationProvider}.
|
* Tests {@link DaoAuthenticationProvider}.
|
||||||
|
@ -399,6 +405,80 @@ public class DaoAuthenticationProviderTests {
|
||||||
assertThat(castResult.getPrincipal()).isEqualTo("rod");
|
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
|
@Test
|
||||||
public void testDetectsNullBeingReturnedFromAuthenticationDao() {
|
public void testDetectsNullBeingReturnedFromAuthenticationDao() {
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||||
|
|
Loading…
Reference in New Issue