Procházet zdrojové kódy

DaoAuthenticationProvider supports password upgrades

Issue: gh-2778
Rob Winch před 7 roky
rodič
revize
7aaf70d582

+ 22 - 0
core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java

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

+ 80 - 0
core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java

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