[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;
}
相关推荐
2601_963870204 小时前
【计算机毕业设计】基于Spring Boot的专科医院医疗管理系统
java·spring boot·课程设计
jufeng13077 小时前
【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】
java·spring boot·时序数据库·tdengine
SQL-First布道者14 小时前
持久层框架的评价标准:只有一个
java·spring boot·spring·tomcat·mybatis·spring jdbc
m0_5873830014 小时前
智慧场馆解决方案实战指南:从系统架构到落地部署全解析
java·spring boot·架构·系统架构
智码看视界15 小时前
Day53-Docker:Dockerfile最佳实践 + 多阶段构建 + docker-compose编排
java·spring boot·docker·docker-compose·dockerfile·容器化部署·多阶段构建
番茄炒鸡蛋加糖16 小时前
SpringBoot 核心原理 + Spring 事务 + 缓存
spring boot·spring·缓存
凤山老林16 小时前
从 RestTemplate 到 HttpClient 5:Spring Boot HTTP 客户端性能调优与连接池治理
spring boot·后端·http
晚染烟17 小时前
每日八股day27
spring boot
边境悍匪17 小时前
蜗牛学苑 Java 智能体学习 Day24|SpringBoot 整合 MyBatis、分层解耦思维导图复盘
java·数据库·spring boot·学习·mybatis
【赫兹威客】浩哥18 小时前
基于SpringBoot+Vue3的企业办公自动化OA系统|集成Activiti工作流引擎
spring boot·后端·课程设计