[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;
}
相关推荐
zimoyin1 小时前
Kotlin 使用 Springboot 反射执行方法并自动传参
spring boot·后端·kotlin
LiuYuHani3 小时前
Spring Boot面试题
java·spring boot·后端
电脑玩家粉色男孩3 小时前
八、Spring Boot 日志详解
java·spring boot·后端
ChinaRainbowSea4 小时前
八. Spring Boot2 整合连接 Redis(超详细剖析)
java·数据库·spring boot·redis·后端·nosql
小咕聊编程5 小时前
【含文档+PPT+源码】基于小程序的智能停车管理系统设计与开发
java·spring boot·小程序
命运之手7 小时前
[ Spring ] Spring Boot Mybatis++ 2025
spring boot·spring·mybatis·mybatis-plus·mybatis++
计算机-秋大田8 小时前
基于微信小程序的实习记录系统设计与实现(LW+源码+讲解)
vue.js·spring boot·后端·微信小程序·小程序·课程设计
飞翔的佩奇8 小时前
Java项目: 基于SpringBoot+mybatis+maven+mysql实现的疾病防控综合管理系统(含源码+数据库+毕业论文)
java·数据库·spring boot·mysql·spring·毕业设计·疾病防控
栗豆包9 小时前
w187社区养老服务平台的设计与实现
java·spring boot·后端·spring·tomcat
violin-wang9 小时前
如何在Intellij IDEA中识别一个文件夹下的多个Maven module?
java·spring boot·spring·maven·intellij-idea