[SpringSecurity5.6.2源码分析二十五]:SwitchUserFilter

74 阅读3分钟

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");

   /**
    * 切换成功跳转的Url
    */
   private String targetUrl;

   /**
    * 切换失败跳转的Url
    */
   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);

}
  • 接下来我们看核心的doFilter(...)方法
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {

   //判断是否是切换用户的请求
   if (requiresSwitchUser(request)) {
      // if set, attempt switch and store original
      try {
         //试切换到另一个用户。如果用户不存在或不活动,则返回null
         Authentication targetUser = attemptSwitchUser(request);

         // 更新线程级别的安全上下文
         SecurityContext context = SecurityContextHolder.createEmptyContext();
         context.setAuthentication(targetUser);
         SecurityContextHolder.setContext(context);
         this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
         // 执行切换用户成功的处理器,一般是重定向到某个Url
         this.successHandler.onAuthenticationSuccess(request, response, targetUser);
      }
      catch (AuthenticationException ex) {
         this.logger.debug("Failed to switch user", ex);
         // 执行切换用户失败的处理器,一般是重定向到某个Url
         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));
      // 执行切换用户成功的处理器,一般是重定向到某个Url
      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);
   // set details
   targetUserRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
   return targetUserRequest;
}
  • 这里的重点就在于如果想退出切换用户的情况下,是如何感知当年的认证对象呢
  • 这里是将原认证对象包装为SwitchUserGrantedAuthority,当做一个权限点放入新的认证对象中