[SpringSecurity5.6.2源码分析二十一]:SecurityContextHolderAwareRequestFilter

前言

  • 从Servlet 3.0开始,在HttpServletRequest中就支持登录,登出等操作了
  • 所以SpringSecurity也有一个过滤器会对HttpServletRequest进行包装,以支持通过HttpServletRequest进行登录登出等操作

1. ServletApiConfigurer

  • ServletApiConfigurer正是SecurityContextHolderAwareRequestFilter对应的配置类,也是默认开启的配置类之一
  • 此配置类中可以调用的方法很少,只有rolePrefix(...)和configure(...)

1.1 rolePrefix(...)

  • rolePrefix(...):用户更新角色前缀
    • 后面用于判断用户是否拥有指定角色
java 复制代码
public ServletApiConfigurer<H> rolePrefix(String rolePrefix) {
   this.securityContextRequestFilter.setRolePrefix(rolePrefix);
   return this;
}

1.2 configure(...)

  • configure(...):由于此过滤器是为了让HttpServletRequest支持认证等操作,所以说这里就设置了认证相关的对象
java 复制代码
public void configure(H http) {
   //设置局部认证管理器
   this.securityContextRequestFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

   //设置身份认证入口点
   ExceptionHandlingConfigurer<H> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
   AuthenticationEntryPoint authenticationEntryPoint = (exceptionConf != null)
         ? exceptionConf.getAuthenticationEntryPoint(http) : null;
   this.securityContextRequestFilter.setAuthenticationEntryPoint(authenticationEntryPoint);

   //设置登出处理器
   LogoutConfigurer<H> logoutConf = http.getConfigurer(LogoutConfigurer.class);
   List<LogoutHandler> logoutHandlers = (logoutConf != null) ? logoutConf.getLogoutHandlers() : null;
   this.securityContextRequestFilter.setLogoutHandlers(logoutHandlers);

   //设置认证对象解析器
   AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
   if (trustResolver != null) {
      this.securityContextRequestFilter.setTrustResolver(trustResolver);
   }

   //设置角色前缀
   ApplicationContext context = http.getSharedObject(ApplicationContext.class);
   if (context != null) {
      String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
      if (grantedAuthorityDefaultsBeanNames.length == 1) {
         GrantedAuthorityDefaults grantedAuthorityDefaults = context
               .getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
         this.securityContextRequestFilter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
      }
   }
   this.securityContextRequestFilter = postProcess(this.securityContextRequestFilter);
   http.addFilter(this.securityContextRequestFilter);
}

2. SecurityContextHolderAwareRequestFilter

  • 此过滤器的doFilter(...)方法很简单,就是通过HttpServletRequestFactory包装Request
java 复制代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
   chain.doFilter(
         //包装Request
         this.requestFactory.create((HttpServletRequest) req
               , (HttpServletResponse) res), res);
}

2.1 HttpServletRequestFactory

  • HttpServletRequestFactory:用于包装 HttpServletRequest 的内部接口
java 复制代码
interface HttpServletRequestFactory {

   HttpServletRequest create(HttpServletRequest request, HttpServletResponse response);

}
  • HttpServletRequestFactory只有一个实现:HttpServlet3RequestFactory
    • HttpServlet3RequestFactory:这个实现类的核心就在于将HttpServletRequest包装为了Servlet3SecurityContextHolderAwareRequestWrapper
    • Servlet3SecurityContextHolderAwareRequestWrapper:支持Servlet 3.0的登录,登出,异步等操作的包装类
java 复制代码
@Override
public HttpServletRequest create(HttpServletRequest request, HttpServletResponse response) {
   return new Servlet3SecurityContextHolderAwareRequestWrapper(request, this.rolePrefix, response);
}
  • SpringSecurity中不仅仅只有这个过滤器将HttpServletRequest进行了包装,像HeaderWriterFilter也包装了,这就是装饰模式的体现

2.2 Servlet3Security...Wrapper

  • Servlet3SecurityContextHolderAwareRequestWrapper对于HttpServletRequest中有关认证的方法都进行了重写
    • authenticate(...):重新进行认证
    • login(...):进行认证
    • logout(...):进行登出
    • ...

2.2.1 authenticate(...)

  • authenticate(...):重新进行认证,如若没有进行认证那就重定向到登录页
java 复制代码
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
   //获得身份认证入口点
   //一般情况都是重定向到登录页的
   AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;
   if (entryPoint == null) {
      HttpServlet3RequestFactory.this.logger.debug(
            "authenticationEntryPoint is null, so allowing original HttpServletRequest to handle authenticate");
      return super.authenticate(response);
   }
   if (isAuthenticated()) {
      return true;
   }
   //执行
   entryPoint.commence(this, response,
         new AuthenticationCredentialsNotFoundException("User is not Authenticated"));
   return false;
}
  • 这里是通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()是否为空来判断认证情况的
java 复制代码
/** 
* 获得认证对象中主体(Principal) * 一般情况都是User或者其子类 
*/
public Principal getUserPrincipal() {
   Authentication auth = getAuthentication();
   if ((auth == null) || (auth.getPrincipal() == null)) {
      return null;
   }
   return auth;
}


/**
 * 获得认证对象
 * 注意:匿名用户视为未登陆
 */
private Authentication getAuthentication() {
   Authentication auth = SecurityContextHolder.getContext().getAuthentication();
   return (!this.trustResolver.isAnonymous(auth)) ? auth : null;
}

2.2.2 login(...)

  • login(...):调用认证管理器进行认证
java 复制代码
public void login(String username, String password) throws ServletException {
   if (isAuthenticated()) {
      throw new ServletException("Cannot perform login for '" + username + "' already authenticated as '"
            + getRemoteUser() + "'");
   }
   AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;
   if (authManager == null) {
      HttpServlet3RequestFactory.this.logger.debug(
            "authenticationManager is null, so allowing original HttpServletRequest to handle login");
      super.login(username, password);
      return;
   }
   //调用局部认证管理器进行认证
   Authentication authentication = getAuthentication(authManager, username, password);

   //创建安全上下文,并放入线程级别的安全上下文策略中
   SecurityContext context = SecurityContextHolder.createEmptyContext();
   context.setAuthentication(authentication);
   SecurityContextHolder.setContext(context);
}

2.2.3 logout(...)

  • logout(...):退出登录
java 复制代码
@Override
public void logout() throws ServletException {
   List<LogoutHandler> handlers = HttpServlet3RequestFactory.this.logoutHandlers;
   if (CollectionUtils.isEmpty(handlers)) {
      HttpServlet3RequestFactory.this.logger
            .debug("logoutHandlers is null, so allowing original HttpServletRequest to handle logout");
      super.logout();
      return;
   }
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
   //调用登出处理器,进行登出
   //比如说清空Csrf的处理器
   for (LogoutHandler handler : handlers) {
      handler.logout(this, this.response, authentication);
   }
}

2.2.4 isUserInRole(...)

  • isUserInRole(...):判断当前用户是否拥有指定角色
java 复制代码
public boolean isUserInRole(String role) {
   return isGranted(role);
}

/**
 * 判断当前用户是否拥有某个角色
 * @param role
 * @return
 */
private boolean isGranted(String role) {
   Authentication auth = getAuthentication();
   //添加角色前缀
   if (this.rolePrefix != null && role != null && !role.startsWith(this.rolePrefix)) {
      role = this.rolePrefix + role;
   }
   if ((auth == null) || (auth.getPrincipal() == null)) {
      return false;
   }
   Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
   if (authorities == null) {
      return false;
   }
   for (GrantedAuthority grantedAuthority : authorities) {
      if (role.equals(grantedAuthority.getAuthority())) {
         return true;
      }
   }
   return false;
}
相关推荐
汤姆yu3 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶3 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip4 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
JavaGuide4 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot
figo10tf5 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
zhangyi_viva5 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端
橙露5 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot
程序员敲代码吗5 小时前
Spring Boot与Tomcat整合的内部机制与优化
spring boot·后端·tomcat
NuageL5 小时前
原始Json字符串转化为Java对象列表/把中文键名变成英文键名
java·spring boot·json
jzheng86106 小时前
Spring Boot(快速上手)
java·spring boot·后端