1. SwitchUserFilter
- SwitchUserFilter是SpringSecurity中最后一个过滤器,他的作用就是切换当前认证的用户
- 比如说我作为管理员我没A用户的密码,我可以直接通过接口切换为A用户
- 这个类没有对应的配置类也不是默认的过滤器之一,需要做以下配置
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilter(switchUserFilter());
}
private SwitchUserFilter switchUserFilter() {
SwitchUserFilter switchUserFilter = new SwitchUserFilter();
switchUserFilter.setUserDetailsService(userDetailsService());
switchUserFilter.setTargetUrl("/login");
switchUserFilter.setSwitchFailureUrl("/error");
return switchUserFilter;
}
- SwitchUserFilter的源码也很简单,基本上都是以前介绍过的类
public class SwitchUserFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
...
private ApplicationEventPublisher eventPublisher;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RequestMatcher exitUserMatcher = createMatcher("/logout/impersonate");
private RequestMatcher switchUserMatcher = createMatcher("/login/impersonate");
private String targetUrl;
private String switchFailureUrl;
private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY;
private String switchAuthorityRole = ROLE_PREVIOUS_ADMINISTRATOR;
private SwitchUserAuthorityChanger switchUserAuthorityChanger;
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private AuthenticationSuccessHandler successHandler;
private AuthenticationFailureHandler failureHandler;
...
}
- 这里面有三个重点
- exitUserMatcher:退出切换用户的请求匹配器
- switchUserMatcher:切换用户的请求匹配器
- switchUserAuthorityChanger:在切换用户时,是否需要变更 切换后用户的 权限
- 这其中陌生的就只有SwitchUserAuthorityChanger,这个类也没具体的实现
public interface SwitchUserAuthorityChanger {
Collection<? extends GrantedAuthority> modifyGrantedAuthorities(UserDetails targetUser,
Authentication currentAuthentication, Collection<? extends GrantedAuthority> authoritiesToBeGranted);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (requiresSwitchUser(request)) {
try {
Authentication targetUser = attemptSwitchUser(request);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(targetUser);
SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
this.successHandler.onAuthenticationSuccess(request, response, targetUser);
}
catch (AuthenticationException ex) {
this.logger.debug("Failed to switch user", ex);
this.failureHandler.onAuthenticationFailure(request, response, ex);
}
return;
}
if (requiresExitUser(request)) {
Authentication originalUser = attemptExitUser(request);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(originalUser);
SecurityContextHolder.setContext(context);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
this.successHandler.onAuthenticationSuccess(request, response, originalUser);
return;
}
this.logger.trace(LogMessage.format("Did not attempt to switch user since request did not match [%s] or [%s]",
this.switchUserMatcher, this.exitUserMatcher));
chain.doFilter(request, response);
}
- attemptSwitchUser(...):尝试切换到另一个用户。如果用户不存在或不活动,则返回null
protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(this.usernameParameter);
username = (username != null) ? username : "";
this.logger.debug(LogMessage.format("Attempting to switch to user [%s]", username));
UserDetails targetUser = this.userDetailsService.loadUserByUsername(username);
this.userDetailsChecker.check(targetUser);
targetUserRequest = createSwitchUserToken(request, targetUser);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
SecurityContextHolder.getContext().getAuthentication(), targetUser));
}
return targetUserRequest;
}
- createSwitchUserToken(...):创建一个新的认证对象
private UsernamePasswordAuthenticationToken createSwitchUserToken(HttpServletRequest request,
UserDetails targetUser) {
UsernamePasswordAuthenticationToken targetUserRequest;
Authentication currentAuthentication = getCurrentAuthentication(request);
GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(this.switchAuthorityRole,
currentAuthentication);
Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();
if (this.switchUserAuthorityChanger != null) {
orig = this.switchUserAuthorityChanger.modifyGrantedAuthorities(targetUser, currentAuthentication, orig);
}
List<GrantedAuthority> newAuths = new ArrayList<>(orig);
newAuths.add(switchAuthority);
targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, targetUser.getPassword(), newAuths);
targetUserRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
return targetUserRequest;
}
- 这里的重点就在于如果想退出切换用户的情况下,是如何感知当年的认证对象呢
- 这里是将原认证对象包装为SwitchUserGrantedAuthority,当做一个权限点放入新的认证对象中