[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;
}
相关推荐
古城小栈10 分钟前
性能测试:JMeter 压测 Spring Boot 微服务
spring boot·jmeter·微服务
算法与双吉汉堡11 分钟前
【短链接项目笔记】Day1 用户模块
java·spring boot·笔记·后端
Selegant16 分钟前
Quarkus vs Spring Boot:谁更适合云原生时代的 Java 开发?
java·spring boot·云原生
计算机毕设指导627 分钟前
基于微信小程序的校园食堂点餐系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·intellij-idea
Mr.Pascal29 分钟前
深度解读一下 springcloud 的 pom.xml 用到的标签
xml·spring boot·spring cloud
Qiuner32 分钟前
Spring Boot AOP(二) 代理机制解析
java·spring boot·后端
invicinble1 小时前
对于设计IT系统的相关思路
spring boot
nvvas1 小时前
JAVA 关于SpringBoot4新版本阅览
java·spring boot
白宇横流学长1 小时前
基于SpringBoot实现的大创管理系统
java·spring boot·后端
梵得儿SHI1 小时前
SpringCloud 核心组件精讲:OpenFeign 实战指南-服务调用优雅实现方案(含自定义拦截器、超时重试、LoadBalance 整合避坑)
spring boot·spring·spring cloud·负载均衡·openfeign的核心应用·微服务调用·熔断组件