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

View File

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