Use SecurityContextHolderStrategy for Switch User

Issue gh-11060
This commit is contained in:
Josh Cummings 2022-06-21 16:43:59 -06:00
parent 98995f2225
commit e1c211c11f
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
2 changed files with 72 additions and 7 deletions

View File

@ -49,6 +49,7 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@ -114,6 +115,9 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
public static final String ROLE_PREVIOUS_ADMINISTRATOR = "ROLE_PREVIOUS_ADMINISTRATOR"; public static final String ROLE_PREVIOUS_ADMINISTRATOR = "ROLE_PREVIOUS_ADMINISTRATOR";
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ApplicationEventPublisher eventPublisher; private ApplicationEventPublisher eventPublisher;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource(); private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
@ -175,9 +179,9 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
try { try {
Authentication targetUser = attemptSwitchUser(request); Authentication targetUser = attemptSwitchUser(request);
// update the current context to the new target user // update the current context to the new target user
SecurityContext context = SecurityContextHolder.createEmptyContext(); SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(targetUser); context.setAuthentication(targetUser);
SecurityContextHolder.setContext(context); this.securityContextHolderStrategy.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser)); this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
// redirect to target url // redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, targetUser); this.successHandler.onAuthenticationSuccess(request, response, targetUser);
@ -192,9 +196,9 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
// get the original authentication object (if exists) // get the original authentication object (if exists)
Authentication originalUser = attemptExitUser(request); Authentication originalUser = attemptExitUser(request);
// update the current context back to the original user // update the current context back to the original user
SecurityContext context = SecurityContextHolder.createEmptyContext(); SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(originalUser); context.setAuthentication(originalUser);
SecurityContextHolder.setContext(context); this.securityContextHolderStrategy.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser)); this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
// redirect to target url // redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser); this.successHandler.onAuthenticationSuccess(request, response, originalUser);
@ -228,7 +232,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
// publish event // publish event
if (this.eventPublisher != null) { if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
SecurityContextHolder.getContext().getAuthentication(), targetUser)); this.securityContextHolderStrategy.getContext().getAuthentication(), targetUser));
} }
return targetUserRequest; return targetUserRequest;
} }
@ -244,7 +248,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
protected Authentication attemptExitUser(HttpServletRequest request) protected Authentication attemptExitUser(HttpServletRequest request)
throws AuthenticationCredentialsNotFoundException { throws AuthenticationCredentialsNotFoundException {
// need to check to see if the current user has a SwitchUserGrantedAuthority // need to check to see if the current user has a SwitchUserGrantedAuthority
Authentication current = SecurityContextHolder.getContext().getAuthentication(); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current == null) { if (current == null) {
throw new AuthenticationCredentialsNotFoundException(this.messages throw new AuthenticationCredentialsNotFoundException(this.messages
.getMessage("SwitchUserFilter.noCurrentUser", "No current user associated with this request")); .getMessage("SwitchUserFilter.noCurrentUser", "No current user associated with this request"));
@ -310,7 +314,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
return attemptExitUser(request); return attemptExitUser(request);
} }
catch (AuthenticationCredentialsNotFoundException ex) { catch (AuthenticationCredentialsNotFoundException ex) {
return SecurityContextHolder.getContext().getAuthentication(); return this.securityContextHolderStrategy.getContext().getAuthentication();
} }
} }
@ -510,6 +514,17 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
this.switchAuthorityRole = switchAuthorityRole; this.switchAuthorityRole = switchAuthorityRole;
} }
/**
* 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;
}
private static RequestMatcher createMatcher(String pattern) { private static RequestMatcher createMatcher(String pattern) {
return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper()); return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper());
} }

View File

@ -36,7 +36,10 @@ import org.springframework.security.core.AuthenticationException;
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.authority.SimpleGrantedAuthority; 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.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.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;
@ -49,8 +52,10 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** /**
@ -416,6 +421,21 @@ public class SwitchUserFilterTests {
assertThat(AuthorityUtils.authorityListToSet(result.getAuthorities())).contains("ROLE_NEW"); assertThat(AuthorityUtils.authorityListToSet(result.getAuthorities())).contains("ROLE_NEW");
} }
@Test
public void doFilterWhenCustomSecurityContextRepositoryThenUses() {
SecurityContextHolderStrategy securityContextHolderStrategy = spy(new MockSecurityContextHolderStrategy(
UsernamePasswordAuthenticationToken.unauthenticated("dano", "hawaii50")));
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextHolderStrategy(securityContextHolderStrategy);
filter.setUserDetailsService(new MockUserDetailsService());
Authentication result = filter.attemptSwitchUser(request);
assertThat(result).isNotNull();
assertThat(result.getName()).isEqualTo("jacklord");
verify(securityContextHolderStrategy, atLeastOnce()).getContext();
}
// SEC-1763 // SEC-1763
@Test @Test
public void nestedSwitchesAreNotAllowed() { public void nestedSwitchesAreNotAllowed() {
@ -512,4 +532,34 @@ public class SwitchUserFilterTests {
} }
static final class MockSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private SecurityContext mock;
private MockSecurityContextHolderStrategy(Authentication authentication) {
this.mock = new SecurityContextImpl(authentication);
}
@Override
public void clearContext() {
this.mock = null;
}
@Override
public SecurityContext getContext() {
return this.mock;
}
@Override
public void setContext(SecurityContext context) {
this.mock = context;
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
} }