首页 > 编程语言 >[SpringSecurity5.6.2源码分析八]:SecurityContextPersistenceFilter

[SpringSecurity5.6.2源码分析八]:SecurityContextPersistenceFilter

时间:2023-09-14 16:05:31浏览次数:38  
标签:SpringSecurity5.6 null request 源码 context SecurityContextPersistenceFilter 上下文 l

前言

  • • 当我们不在其他线程而就在容器创建的线程中使用SecurityContextHolder.getContext()获取SecurityContext的时候,正常都能获取到
  • • SecurityContext默认是放在线程中的,所以说在某个地方一定将SecurityContext放到线程中,而这个类就是SecurityContextPersistenceFilter

1、SecurityContextConfigurer

  • • SecurityContextConfigurer是SecurityContextPersistenceFilter的配置类,是在获取HttpSecurity的时候默认开启的
  • • 这个配置类重点方法就是securityContextRepository(...)方法
  • • 是往SharedObject中注册一个SecurityContextRepository
public SecurityContextConfigurer<H> securityContextRepository(
      SecurityContextRepository securityContextRepository) {
   getBuilder().setSharedObject(SecurityContextRepository.class,
         securityContextRepository);
   return this;
}
  • • 先看SharedObject
  • • 就是一个HashMap,是在各大过滤器中共享数据的地方
  • • 可以通过HttpSecurity.getSharedObjects()操作

[SpringSecurity5.6.2源码分析八]:SecurityContextPersistenceFilter_spring

image.png

1.1 SecurityContextRepository

  • • 看源码就是三个方法
public interface SecurityContextRepository {

   SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
   
   void saveContext(SecurityContext context, HttpServletRequest request,
         HttpServletResponse response);

   boolean containsContext(HttpServletRequest request);
}
  • • loadContext(...):获得当前安全上下文
  • • saveContext(...):保存安全上下文
  • • containsContext(...):查看指定的Request是否包含当前用户的安全上下文

1.2 HttpSessionSecurityContextRepository

  • • SecurityContextRepository只有一个有用的实现,我们看看三大方法的实现:
  • • loadContext(...):从HttpSession加载安全上下文
@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
   HttpServletRequest request = requestResponseHolder.getRequest();
   HttpServletResponse response = requestResponseHolder.getResponse();
   //false表示有就获取,没有就返回空HttpSession
   HttpSession httpSession = request.getSession(false);
   //从HttpSession获取安全存储上下文
   SecurityContext context = readSecurityContextFromSession(httpSession);
   if (context == null) {
      //如果没有找到安全上下文,那就创建一个空安全上下文
      context = generateNewContext();
      if (this.logger.isTraceEnabled()) {
         this.logger.trace(LogMessage.format("Created %s", context));
      }
   }
   //创建response包装类,目的是为了更新安全上下文
   SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
         httpSession != null, context);
   requestResponseHolder.setResponse(wrappedResponse);
   requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
   return context;
}
  • • saveContext(...):通过responseWrapper保存上下文
@Override
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
   SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
         SaveContextOnUpdateOrErrorResponseWrapper.class);
   Assert.state(responseWrapper != null, () -> "Cannot invoke saveContext on response " + response
         + ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
   responseWrapper.saveContext(context);
}
  • • 我们再看SaveToSessionResponseWrapper.saveContext(...)方法:更新存储在HttpSession中的安全上下文 如果AuthenticationTrustResolver将当前用户识别为匿名用户,则不会存储上下文
@Override
protected void saveContext(SecurityContext context) {
   //首先获得认证对象
   final Authentication authentication = context.getAuthentication();
   HttpSession httpSession = this.request.getSession(false);
   String springSecurityContextKey = HttpSessionSecurityContextRepository.this.springSecurityContextKey;
   //如果没有认证对象或者是匿名用户
   if (authentication == null
         || HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {

      //如果是匿名用户和空认证对象那么安全上下文其实已经没有任何意义,如果存在就删除它
      if (httpSession != null && this.authBeforeExecution != null) {
         //删除存储在HttpSession中的安全上下文
         httpSession.removeAttribute(springSecurityContextKey);
         this.isSaveContextInvoked = true;
      }
      if (this.logger.isDebugEnabled()) {
         if (authentication == null) {
            this.logger.debug("Did not store empty SecurityContext");
         }
         else {
            this.logger.debug("Did not store anonymous SecurityContext");
         }
      }
      return;
   }
   //如果为空就创建新的HttpSession
   httpSession = (httpSession != null) ? httpSession : createNewSessionIfAllowed(context, authentication);
   //如果HttpSession存在,存储当前的安全上下文
   //但仅当它在此线程中发生了变化
   if (httpSession != null) {
      //可能是一个新的会话,所以还要检查上下文属性
      if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
         httpSession.setAttribute(springSecurityContextKey, context);
         this.isSaveContextInvoked = true;
         if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, httpSession));
         }
      }
   }
}
  • • 我们再回到containsContext(...)方法:此方法主要是SessionManagementFilter中用,后面再讲
@Override
public boolean containsContext(HttpServletRequest request) {
   HttpSession session = request.getSession(false);
   if (session == null) {
      return false;
   }
   return session.getAttribute(this.springSecurityContextKey) != null;
}

1.3 SecurityContextConfigurer.configure(...)

  • • 现在我们回到配置类中,根据SpirngSecurity的建筑者模式,配置类只重写了configure(...)方法
@Override
@SuppressWarnings("unchecked")
public void configure(H http) {
   //获得HttpSession级别的安全上下文存储策略
   SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
   //如果没有就创建默认的
   if (securityContextRepository == null) {
      securityContextRepository = new HttpSessionSecurityContextRepository();
   }
   //创建过滤器
   SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
         securityContextRepository);
   //从HttpSecurity中获得会话管理配配置类
   SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
   SessionCreationPolicy sessionCreationPolicy = (sessionManagement != null)
         ? sessionManagement.getSessionCreationPolicy() : null;
   //看会话管理配配置类是否允许一直创建Session
   //这样的话SecurityContextPersistenceFilter就直接使用request.getSession()创建session
   if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
      securityContextFilter.setForceEagerSessionCreation(true);
   }
   securityContextFilter = postProcess(securityContextFilter);
   http.addFilter(securityContextFilter);
}
  • • 这里唯一没讲的就是通过SessionManagementConfigurer获取SessionCreationPolicy
  • • 因为安全上下文存储策略默认只有一个HttpSession的实现
  • • 如果是不允许一直创建,那么在SecurityContextPersistenceFilter中获取HttpSession就不会执行下面的代码了
if (this.forceEagerSessionCreation) {
   HttpSession session = request.getSession();
   if (this.logger.isDebugEnabled() && session.isNew()) {
      this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
   }
}
  • • 再看下SessionCreationPolicy的源码
/**
 * Spring Security的过滤器在执行过程中是否允许创建会话的策略
 * <li>比如说:{@link org.springframework.security.web.context.SecurityContextPersistenceFilter#doFilter(ServletRequest, ServletResponse, FilterChain)}</li>
 */
public enum SessionCreationPolicy {

   /**
    * 总是 {@link HttpSession}
    */
   ALWAYS,

   /**
    * 永远不会创建 {@link HttpSession}, 除非他已经存在
    * 应该不会由Spring Security创建
    */
   NEVER,

   /**
    * 在需要的时候创建 {@link HttpSession}
    */
   IF_REQUIRED,

   /**
    * Spring Security永远不会创建 {@link HttpSession},也永远不会使用它获取 {@link HttpSession}
    */
   STATELESS

}

2、SecurityContextPersistenceFilter

  • • 此过滤器是为了从HttpSession级别的安全上下文存储策略中读取安全上下文,然后放到线程级别的安全上下文策略中,方便后面程序操作安全上下文
  • • 这里的HttpSession级别的安全上下文存储策略指的是:HttpSessionSecurityContextRepository
  • • 这里的线程级别的安全上下文策略指的是:SecurityContextHolderStrategy的实现类之ThreadLocalSecurityContextHolderStrategy
  • • 看一下关键方法:doFilter(...):
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
         throws IOException, ServletException {
      //确保过滤器器在每个请求中只执行一次
      if (request.getAttribute(FILTER_APPLIED) != null) {
         chain.doFilter(request, response);
         return;
      }
      //标志本次请求已经执行过当前过滤器
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
      //是否允许创建Session
      if (this.forceEagerSessionCreation) {
         HttpSession session = request.getSession();
         if (this.logger.isDebugEnabled() && session.isNew()) {
            this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
         }
      }
      HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
      //从HttpSession级别的安全上下文存储策略中尝试获取安全上下文
      SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
      try {
         //设置到线程级别的安全上下文存储策略中
         //方便后续程序的操作
         SecurityContextHolder.setContext(contextBeforeChainExecution);
         if (contextBeforeChainExecution.getAuthentication() == null) {
            logger.debug("Set SecurityContextHolder to empty SecurityContext");
         }
         else {
            if (this.logger.isDebugEnabled()) {
               this.logger
                     .debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
            }
         }
         chain.doFilter(holder.getRequest(), holder.getResponse());
      }
      finally {
         //这里是已经执行完Controller的代码

         //先拿到当前用户的线程级别的安全上下文
         SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
         //清空
         SecurityContextHolder.clearContext();
         //由于用户可能修改过线程级别的安全上下文
         //所有重新设置到HttpSession的线程级别的安全上下文策略中
         this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
         request.removeAttribute(FILTER_APPLIED);
         this.logger.debug("Cleared SecurityContextHolder to complete request");
      }
   }
  • • 第23行代码就是将上下文放入到SecurityContextHolderStrategy中
  • • 然后我们可以看出方法的后半段就是一个try finally的格式
  • • 第33行代码就是执行后面的过滤器,而SpringMVC的DispatcherServlet也会在其中执行完毕
  • • 所以说一旦开始执行finally方法就代表SecurityContext已经是最新的了,所以说将其重新设置到HttpSesison中


标签:SpringSecurity5.6,null,request,源码,context,SecurityContextPersistenceFilter,上下文,l
From: https://blog.51cto.com/u_14008019/7470823

相关文章

  • springboot智能3D人体导医系统源码
    智能3D人体导医系统源码医院智能导诊系统是在医疗中使用的引导患者自助就诊挂号,在就诊的过程中有许多患者不知道需要挂什么号,要看什么病,通过智能导诊系统,可输入自身疾病的症状表现,或选择身体部位,在经由智慧导诊系统多维度计算,精准推荐科室,引导患者挂号就诊,实现科学就诊,不再担心挂......
  • app直播源码,Vue 禁止输入框输入空格
    app直播源码,Vue禁止输入框输入空格 <template> <div>  <input   type="text"   v-model="text"   @input="(e)=>text=e.target.value.replace(/\s/g,'')"  > </div></template><scri......
  • Ubuntu boost源码安装
    卸载boost库sudorm-f/usr/local/lib/libboost*sudorm-rf/usr/local/include/boostsudorm-r/usr/local/lib/cmake/boost*sudorm-r/usr/local/lib/cmake/Boost*下载需要的boosthttps://www.boost.org/users/history/wgethttps://boostorg.jfrog.io/artifac......
  • 【Tomcat】【源码一】Tomcat 源码分析环境搭建
    1 前言ApacheTomcat®软件是JavaServlet、JavaServer页面、Java语言表达式和JavaWebSocket技术的开源实现。JavaServlet、JavaServer页面、Java表达式语言和JavaWebSocket规范都是在Java社区进程下开发的。ApacheTomcat软件是在开放和参与的环境中开发的,并在A......
  • java课堂开学第一节课测试源码
    1//2//Sourcecoderecreatedfroma.classfilebyIntelliJIDEA3//(poweredbyFernFlowerdecompiler)4//56importjava.io.PrintStream;7importjava.util.ArrayList;8importjava.util.Scanner;910publicclasstest{11......
  • 自助点餐管理系统的设计与实现-计算机毕业设计源码+LW文档
    1. 选题目的、意义及研究现状:1.1目的现代科学技术在快速发展、计算机与全球互连网络相连接,使今天的社会进入了以计算机为核心的社会。计算机的出现给我们诸多方面带来了无限的商机与便利。比如餐饮业,网络就发挥了巨大的作用——网上自助点餐。自助点餐为客户提供的是最方便的饮食......
  • 旅游景区景点系统的设计与实现-计算机毕业设计源码+LW文档
    选题意义: 旅游业经过多年的发展,已经从单一的观光旅游发展到目前的一站式旅游方向,呈现多元化趋势,为游客提供旅游观光、户外探险、自由行、住宿等各种服务。旅游业是一个国家综合性服务行业,可以为经济发展提供助力。对于大众来说,通过旅游可以提高生活质量,帮助人们更加有效的完成工......
  • 基于Django的社区疫情管理平台的设计与实现-计算机毕业设计源码+LW文档
    一、研究的背景和意义研究背景:2020年初,新冠疫情在武汉爆发,造成多人感染,社会治安和医疗体系面临巨大挑战。在这前所未有的严峻形式中,国家领导统一指挥,全面部署,积极应对挑战。社区是最小单位,社区防控做好才能取得关键性胜利。在没有经验基础的情况下,各社区除了执行政策条例外,积极摸......
  • 企业执勤管理系统的设计与实现-计算机毕业设计源码+LW文档
    摘 要随着信息技术的发展,管理系统越来越成熟,各种企事业单位使用各种类型的管理系统来提高工作效率,从而降低手工劳动的弊端。企业一直以来都非常重视公司信息化的发展,近几年来随着公司规模扩大,业务逐渐增加,企业对员工的管理也愈发的困难。因此,公司提出通过开发基于Idea的企业执......
  • 弹幕小游戏源码设计重点解析
      弹幕小游戏开发的特点就是以互动性和与类型为主,更重要的还是以钱赚,在开发设计弹幕游戏时需要注意那些要点:  1.弹幕游戏的UI设计:弹幕小游戏的UI需要简洁明了,易于操作。重点需要设计一个直观、快速的交互方式,使用户能够轻松参与并理解游戏规则。  2.弹幕游戏逻辑设计......