Browse Source

Use SecurityContextHolderStrategy for Database Support

Issue gh-11060
Josh Cummings 3 years ago
parent
commit
ec1bfa12f0

+ 16 - 1
core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java

@@ -31,6 +31,7 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsPasswordService;
@@ -55,6 +56,9 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
 
 	private final Map<String, MutableUserDetails> users = new HashMap<>();
 
+	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+			.getContextHolderStrategy();
+
 	private AuthenticationManager authenticationManager;
 
 	public InMemoryUserDetailsManager() {
@@ -113,7 +117,7 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
 
 	@Override
 	public void changePassword(String oldPassword, String newPassword) {
-		Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
+		Authentication currentUser = this.securityContextHolderStrategy.getContext().getAuthentication();
 		if (currentUser == null) {
 			// This would indicate bad coding somewhere
 			throw new AccessDeniedException(
@@ -154,6 +158,17 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai
 				user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
 	}
 
+	/**
+	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
+	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
+	 *
+	 * @since 5.8
+	 */
+	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
+		Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
+		this.securityContextHolderStrategy = securityContextHolderStrategy;
+	}
+
 	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
 		this.authenticationManager = authenticationManager;
 	}

+ 18 - 3
core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java

@@ -40,6 +40,7 @@ import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserCache;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -108,6 +109,9 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
 
 	protected final Log logger = LogFactory.getLog(getClass());
 
+	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+			.getContextHolderStrategy();
+
 	private String createUserSql = DEF_CREATE_USER_SQL;
 
 	private String deleteUserSql = DEF_DELETE_USER_SQL;
@@ -260,7 +264,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
 
 	@Override
 	public void changePassword(String oldPassword, String newPassword) throws AuthenticationException {
-		Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
+		Authentication currentUser = this.securityContextHolderStrategy.getContext().getAuthentication();
 		if (currentUser == null) {
 			// This would indicate bad coding somewhere
 			throw new AccessDeniedException(
@@ -280,9 +284,9 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
 		this.logger.debug("Changing password for user '" + username + "'");
 		getJdbcTemplate().update(this.changePasswordSql, newPassword, username);
 		Authentication authentication = createNewAuthentication(currentUser, newPassword);
-		SecurityContext context = SecurityContextHolder.createEmptyContext();
+		SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
 		context.setAuthentication(authentication);
-		SecurityContextHolder.setContext(context);
+		this.securityContextHolderStrategy.setContext(context);
 		this.userCache.removeUserFromCache(username);
 	}
 
@@ -419,6 +423,17 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
 		return getJdbcTemplate().queryForObject(this.findGroupIdSql, Integer.class, group);
 	}
 
+	/**
+	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
+	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
+	 *
+	 * @since 5.8
+	 */
+	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
+		Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
+		this.securityContextHolderStrategy = securityContextHolderStrategy;
+	}
+
 	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
 		this.authenticationManager = authenticationManager;
 	}

+ 19 - 1
core/src/test/java/org/springframework/security/provisioning/InMemoryUserDetailsManagerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,12 +20,19 @@ import java.util.Properties;
 
 import org.junit.jupiter.api.Test;
 
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
+import org.springframework.security.core.context.SecurityContextImpl;
 import org.springframework.security.core.userdetails.PasswordEncodedUser;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 /**
  * @author Rob Winch
@@ -79,4 +86,15 @@ public class InMemoryUserDetailsManagerTests {
 				.withMessage("The entry with username 'joe' could not be converted to an UserDetails");
 	}
 
+	@Test
+	public void changePasswordWhenCustomSecurityContextHolderStrategyThenUses() {
+		Authentication authentication = TestAuthentication.authenticatedUser();
+		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager((User) authentication.getPrincipal());
+		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		manager.setSecurityContextHolderStrategy(strategy);
+		manager.changePassword("password", "newpassword");
+		verify(strategy).getContext();
+	}
+
 }

+ 15 - 0
core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java

@@ -39,6 +39,8 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
+import org.springframework.security.core.context.SecurityContextImpl;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserCache;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -48,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 /**
  * Tests for {@link JdbcUserDetailsManager}
@@ -205,6 +208,18 @@ public class JdbcUserDetailsManagerTests {
 		assertThat(this.cache.getUserMap().containsKey("joe")).isFalse();
 	}
 
+	@Test
+	public void changePasswordWhenCustomSecurityContextHolderStrategyThenUses() {
+		insertJoe();
+		Authentication authentication = authenticateJoe();
+		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl());
+		this.manager.setSecurityContextHolderStrategy(strategy);
+		this.manager.changePassword("wrongpassword", "newPassword");
+		verify(strategy).getContext();
+	}
+
 	@Test
 	public void changePasswordSucceedsWithIfReAuthenticationSucceeds() {
 		insertJoe();