DaoAuthenticationProvider supports password upgrades

Issue: gh-2778
This commit is contained in:
Rob Winch 2018-07-14 17:01:43 -05:00
parent cabd0a5579
commit 7aaf70d582
2 changed files with 102 additions and 0 deletions

View File

@ -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;
}
} }

View File

@ -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(