소스 검색

UserDetailsRepositoryReactiveAuthenticationManager uses ReactiveUserDetailsPasswordService

Issue: gh-2778
Rob Winch 7 년 전
부모
커밋
72a267a311

+ 25 - 2
core/src/main/java/org/springframework/security/authentication/UserDetailsRepositoryReactiveAuthenticationManager.java

@@ -18,7 +18,9 @@ package org.springframework.security.authentication;
 
 import org.springframework.security.core.Authentication;
 
+import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
 import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.util.Assert;
@@ -38,6 +40,8 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
 
 	private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
 
+	private ReactiveUserDetailsPasswordService userDetailsPasswordService;
+
 	private Scheduler scheduler = Schedulers.parallel();
 
 	public UserDetailsRepositoryReactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService) {
@@ -48,11 +52,21 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
 	@Override
 	public Mono<Authentication> authenticate(Authentication authentication) {
 		final String username = authentication.getName();
+		final String presentedPassword = (String) authentication.getCredentials();
 		return this.userDetailsService.findByUsername(username)
 				.publishOn(this.scheduler)
-				.filter( u -> this.passwordEncoder.matches((String) authentication.getCredentials(), u.getPassword()))
+				.filter(u -> this.passwordEncoder.matches(presentedPassword, u.getPassword()))
 				.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
-				.map( u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()) );
+				.flatMap(u -> {
+					boolean upgradeEncoding = this.userDetailsPasswordService != null
+							&& this.passwordEncoder.upgradeEncoding(u.getPassword());
+					if (upgradeEncoding) {
+						String newPassword = this.passwordEncoder.encode(presentedPassword);
+						return this.userDetailsPasswordService.updatePassword(u, newPassword);
+					}
+					return Mono.just(u);
+				})
+				.map(u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()) );
 	}
 
 	/**
@@ -80,4 +94,13 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
 		Assert.notNull(scheduler, "scheduler cannot be null");
 		this.scheduler = scheduler;
 	}
+
+	/**
+	 * Sets the service to use for upgrading passwords on successful authentication.
+	 * @param userDetailsPasswordService the service to use
+	 */
+	public void setUserDetailsPasswordService(
+			ReactiveUserDetailsPasswordService userDetailsPasswordService) {
+		this.userDetailsPasswordService = userDetailsPasswordService;
+	}
 }

+ 58 - 1
core/src/test/java/org/springframework/security/authentication/UserDetailsRepositoryReactiveAuthenticationManagerTests.java

@@ -22,6 +22,7 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
 import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -32,7 +33,9 @@ import reactor.core.scheduler.Schedulers;
 
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -47,6 +50,9 @@ public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
 	@Mock
 	private PasswordEncoder encoder;
 
+	@Mock
+	private ReactiveUserDetailsPasswordService userDetailsPasswordService;
+
 	@Mock
 	private Scheduler scheduler;
 
@@ -79,10 +85,61 @@ public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
 		this.manager.setScheduler(this.scheduler);
 		this.manager.setPasswordEncoder(this.encoder);
 		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
-			this.user, this.user.getPassword());
+				this.user, this.user.getPassword());
 
 		Authentication result = this.manager.authenticate(token).block();
 
 		verify(this.scheduler).schedule(any());
 	}
+
+	@Test
+	public void authenticateWhenPasswordServiceThenUpdated() {
+		String encodedPassword = "encoded";
+		when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
+		when(this.encoder.matches(any(), any())).thenReturn(true);
+		when(this.encoder.upgradeEncoding(any())).thenReturn(true);
+		when(this.encoder.encode(any())).thenReturn(encodedPassword);
+		when(this.userDetailsPasswordService.updatePassword(any(), any())).thenReturn(Mono.just(this.user));
+		this.manager.setPasswordEncoder(this.encoder);
+		this.manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				this.user, this.user.getPassword());
+
+		Authentication result = this.manager.authenticate(token).block();
+
+		verify(this.encoder).encode(this.user.getPassword());
+		verify(this.userDetailsPasswordService).updatePassword(eq(this.user), eq(encodedPassword));
+	}
+
+	@Test
+	public void authenticateWhenPasswordServiceAndBadCredentialsThenNotUpdated() {
+		when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
+		when(this.encoder.matches(any(), any())).thenReturn(false);
+		when(this.userDetailsPasswordService.updatePassword(any(), any())).thenReturn(Mono.just(this.user));
+		this.manager.setPasswordEncoder(this.encoder);
+		this.manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				this.user, this.user.getPassword());
+
+		assertThatThrownBy(() -> this.manager.authenticate(token).block())
+			.isInstanceOf(BadCredentialsException.class);
+
+		verifyZeroInteractions(this.userDetailsPasswordService);
+	}
+
+	@Test
+	public void authenticateWhenPasswordServiceAndUpgradeFalseThenNotUpdated() {
+		when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
+		when(this.encoder.matches(any(), any())).thenReturn(true);
+		when(this.encoder.upgradeEncoding(any())).thenReturn(false);
+		when(this.userDetailsPasswordService.updatePassword(any(), any())).thenReturn(Mono.just(this.user));
+		this.manager.setPasswordEncoder(this.encoder);
+		this.manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				this.user, this.user.getPassword());
+
+		Authentication result = this.manager.authenticate(token).block();
+
+		verifyZeroInteractions(this.userDetailsPasswordService);
+	}
 }