[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;
}
相关推荐
我叫张土豆11 小时前
Swagger MCP 实战:把 OpenAPI 变成可控的 MCP 工具(Spring Boot + Spring AI)
人工智能·spring boot·spring
亓才孓11 小时前
[SpringBoot]@SpringBootTest标签作用
java·spring boot·log4j
弹简特11 小时前
【JavaEE09-后端部分】SpringMVC04-SpringMVC第三大核心-处理响应和@RequestMapping详解
java·spring boot·spring·java-ee·tomcat
树码小子11 小时前
图书管理系统(1)项目准备,用户登录接口,添加图书接口
spring boot
kong790692811 小时前
SpringBoot原理
java·spring boot·后端
那我掉的头发算什么11 小时前
【图书管理系统】基于Spring全家桶的图书管理系统(下)
java·数据库·spring boot·后端·spring·mybatis
前路不黑暗@1 天前
Java项目:Java脚手架项目的文件服务(八)
java·开发语言·spring boot·学习·spring cloud·docker·maven
计算机毕设vx_bysj68691 天前
计算机毕业设计必看必学~基于SpringBoot校园招聘系统的设计与实现,原创定制程序、单片机、java、PHP、Python、小程序、文案全套、毕设成品等!
java·spring boot·mysql·课程设计
Moshow郑锴1 天前
Java SpringBoot 疑难 Bug 排查思路解析:从“语法正确”到“行为相符”
java·spring boot·bug