Save the SecurityContext when switching user

Closes gh-12504
This commit is contained in:
Marcus Da Coregio 2023-01-09 15:19:07 -03:00
parent 225dc593a8
commit ffdb397830
2 changed files with 79 additions and 0 deletions

View File

@ -58,6 +58,8 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
@ -142,6 +144,8 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
private AuthenticationFailureHandler failureHandler; private AuthenticationFailureHandler failureHandler;
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
Assert.notNull(this.userDetailsService, "userDetailsService must be specified"); Assert.notNull(this.userDetailsService, "userDetailsService must be specified");
@ -179,6 +183,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
context.setAuthentication(targetUser); context.setAuthentication(targetUser);
SecurityContextHolder.setContext(context); SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser)); this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
this.securityContextRepository.saveContext(context, request, response);
// redirect to target url // redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, targetUser); this.successHandler.onAuthenticationSuccess(request, response, targetUser);
} }
@ -196,6 +201,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
context.setAuthentication(originalUser); context.setAuthentication(originalUser);
SecurityContextHolder.setContext(context); SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser)); this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
this.securityContextRepository.saveContext(context, request, response);
// redirect to target url // redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser); this.successHandler.onAuthenticationSuccess(request, response, originalUser);
return; return;
@ -510,6 +516,19 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
this.switchAuthorityRole = switchAuthorityRole; this.switchAuthorityRole = switchAuthorityRole;
} }
/**
* Sets the {@link SecurityContextRepository} to save the {@link SecurityContext} on
* switch user success. The default is
* {@link RequestAttributeSecurityContextRepository}.
* @param securityContextRepository the {@link SecurityContextRepository} to use.
* Cannot be null.
* @since 5.7.7
*/
public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = securityContextRepository;
}
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

@ -16,15 +16,18 @@
package org.springframework.security.web.authentication.switchuser; package org.springframework.security.web.authentication.switchuser;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.AccountExpiredException;
@ -44,11 +47,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.util.FieldUtils; import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
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.ArgumentMatchers.any;
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.verify; import static org.mockito.Mockito.verify;
@ -483,6 +490,59 @@ public class SwitchUserFilterTests {
filter.setSwitchFailureUrl("/foo"); filter.setSwitchFailureUrl("/foo");
} }
@Test
void filterWhenDefaultSecurityContextRepositoryThenRequestAttributeRepository() {
SwitchUserFilter switchUserFilter = new SwitchUserFilter();
assertThat(ReflectionTestUtils.getField(switchUserFilter, "securityContextRepository"))
.isInstanceOf(RequestAttributeSecurityContextRepository.class);
}
@Test
void doFilterWhenSwitchUserThenSaveSecurityContext() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
request.setParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/login/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.setUserDetailsService(new MockUserDetailsService());
filter.setTargetUrl("/target");
filter.afterPropertiesSet();
filter.doFilter(request, response, filterChain);
verify(securityContextRepository).saveContext(any(), any(), any());
}
@Test
void doFilterWhenExitUserThenSaveSecurityContext() throws ServletException, IOException {
UsernamePasswordAuthenticationToken source = UsernamePasswordAuthenticationToken.authenticated("dano",
"hawaii50", ROLES_12);
// set current user (Admin)
List<GrantedAuthority> adminAuths = new ArrayList<>(ROLES_12);
adminAuths.add(new SwitchUserGrantedAuthority("PREVIOUS_ADMINISTRATOR", source));
UsernamePasswordAuthenticationToken admin = UsernamePasswordAuthenticationToken.authenticated("jacklord",
"hawaii50", adminAuths);
SecurityContextHolder.getContext().setAuthentication(admin);
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
request.setParameter(SwitchUserFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "jacklord");
request.setRequestURI("/logout/impersonate");
SwitchUserFilter filter = new SwitchUserFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.setUserDetailsService(new MockUserDetailsService());
filter.setTargetUrl("/target");
filter.afterPropertiesSet();
filter.doFilter(request, response, filterChain);
verify(securityContextRepository).saveContext(any(), any(), any());
}
private class MockUserDetailsService implements UserDetailsService { private class MockUserDetailsService implements UserDetailsService {
private String password = "hawaii50"; private String password = "hawaii50";