当前位置: 首页 > article >正文

SpringSecurity原理解析(三):请求流转过程

1、当web系统启动的时候SpringSecurity做了哪些事情?

      当web系统启动的时候会加载WEB-INF下的web.xml文件,在web.xml主要配置了下边几块的

      内容,分别是:

              1)加载classpath路径下的配置文件(包括SpringSecurity的配置文件),并根据配置文

                   件来初始化spring容器

                   spring初始化操作与 SpringSecurity有关系的操作是:加载SpringSecurity配置文件,

                   将相关的数据添加到Spring容器中。

              2)初始化SpringMVC的前端控制器

                    SpringMVC的初始化和SpringSecurity其实是没有多大关系的

              3)加载 SpringSecurity 过滤器 DelegatingFilterProxy

      web.xml配置文件入下:

 <!-- 初始化spring容器 -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>

      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>

      <!-- post乱码过滤器 -->
      <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
        </init-param>
      </filter>

      <!-- 配置过滤器链 springSecurityFilterChain 名称固定 -->
      <filter>
         <filter-name>springSecurityFilterChain</filter-name>
         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
      <filter-mapping>
         <filter-name>springSecurityFilterChain</filter-name>
          <!--配置拦截哪些请求
              /* 表示所有的请求都想需要经过 DelegatingFilterProxy 
          -->
         <url-pattern>/*</url-pattern>
      </filter-mapping>

      <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>

      <!-- 前端控制器 -->
      <servlet>
        <servlet-name>dispatcherServletb</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
        <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>

      <servlet-mapping>
        <servlet-name>dispatcherServletb</servlet-name>
        <!-- 拦截所有请求jsp除外 -->
        <url-pattern>/</url-pattern>
      </servlet-mapping>

      

2、DelegatingFilterProxy 过滤器

     DelegatingFilterProxy过滤器:用于拦截请求,这里配置“/*” 表示拦截所有的请求。

     有一点要注意,DelegatingFilterProxy 并不是SpringSecurity提供的,本身是和SpringSecurity

     没有关系的,而是由 spring 提供的,是spring 为第三方组件提供的通用过滤器,当第三方组

    件需要对spring扩展时,只需要把自身的过滤器交给DelegatingFilterProxy 管理就行了。

    除了 SpringSecurity外,像Shiro中也用到了 DelegatingFilterProxy

     DelegatingFilterProxy 的主要功能就是从Spring IOC容器中获取 DelegatingFilterProxy 这个过

     滤器配置的FileterName对应的对象,这个对象就是第三方的过滤器。

      DelegatingFilterProxy 类结构如下: 

public class DelegatingFilterProxy extends GenericFilterBean {
    @Nullable
    private String contextAttribute;
	//web容器上下文对象,也表示spring ioc容器
    @Nullable
    private WebApplicationContext webApplicationContext;
	//目标对象,即delegate对象的名称
    //todo 注意:是我们在web.xml中filter-name标签配置的名称
    @Nullable
    private String targetBeanName;
	//标识是否是 目标过滤器对象(即delegate)的生命周期 之内
    private boolean targetFilterLifecycle;
	//DelegatingFilterProxy 代理对象的委托对象(是一个过滤器),即第三方的过滤器
    @Nullable
    private volatile Filter delegate;
	//锁对象
    private final Object delegateMonitor;

    public DelegatingFilterProxy() {
        this.targetFilterLifecycle = false;
        this.delegateMonitor = new Object();
    }

    public DelegatingFilterProxy(Filter delegate) {
        this.targetFilterLifecycle = false;
        this.delegateMonitor = new Object();
        Assert.notNull(delegate, "Delegate Filter must not be null");
        this.delegate = delegate;
    }

    public DelegatingFilterProxy(String targetBeanName) {
        this(targetBeanName, (WebApplicationContext)null);
    }

    public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
        this.targetFilterLifecycle = false;
        this.delegateMonitor = new Object();
        Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
        this.setTargetBeanName(targetBeanName);
        this.webApplicationContext = wac;
        if (wac != null) {
            this.setEnvironment(wac.getEnvironment());
        }

    }

    public void setContextAttribute(@Nullable String contextAttribute) {
        this.contextAttribute = contextAttribute;
    }

    @Nullable
    public String getContextAttribute() {
        return this.contextAttribute;
    }

    //设置目标对象,即委托对象的bean名称
	//委托对象的beanName是我们在web.xml中filter-name标签配置的名称,如:springSecurityFilterChain
    public void setTargetBeanName(@Nullable String targetBeanName) {
        this.targetBeanName = targetBeanName;
    }

    @Nullable
    protected String getTargetBeanName() {
        return this.targetBeanName;
    }

    public void setTargetFilterLifecycle(boolean targetFilterLifecycle) {
        this.targetFilterLifecycle = targetFilterLifecycle;
    }

    protected boolean isTargetFilterLifecycle() {
        return this.targetFilterLifecycle;
    }

    protected void initFilterBean() throws ServletException {
        synchronized(this.delegateMonitor) {
		    //由此可见 delegate 是一个单例对象,只初始化一次
            if (this.delegate == null) {
			    //目标过滤器的名称
                if (this.targetBeanName == null) {
				    //获取在web.xml中配置的DelegatingFilterProxy时filter-name标签的名称 springSecurityFilterChain,以 springSecurityFilterChain 作为目标bean的名称
                    this.targetBeanName = this.getFilterName();
                }

                //获取web容器的上下文,即获取spring IOC容器
                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
				    //初始化 delegate 对象
                    this.delegate = this.initDelegate(wac);
                }
            }

        }
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized(this.delegateMonitor) {
                delegateToUse = this.delegate;
				//若当前委托对象为null,则先尝试初始化委托对象 delegate
                if (delegateToUse == null) {
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }

                    delegateToUse = this.initDelegate(wac);
                }

                this.delegate = delegateToUse;
            }
        }
		
        //执行委托对象的过滤操作
        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }

    public void destroy() {
        Filter delegateToUse = this.delegate;
        if (delegateToUse != null) {
            this.destroyDelegate(delegateToUse);
        }

    }

    //获取Web容器的上下文对象,即获取Spring IOC容器
    @Nullable
    protected WebApplicationContext findWebApplicationContext() {
        if (this.webApplicationContext != null) {
            if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
                ConfigurableApplicationContext cac = (ConfigurableApplicationContext)this.webApplicationContext;
                if (!cac.isActive()) {
				    //刷新重启spring容器
                    cac.refresh();
                }
            }

            return this.webApplicationContext;
        } else {
            String attrName = this.getContextAttribute();
            return attrName != null ? WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName) : WebApplicationContextUtils.findWebApplicationContext(this.getServletContext());
        }
    }

    //初始化 Delegate
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = this.getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
		//从web容器中根据bean名称获取bean,规定是一个Filter类型
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
		//判断是否绑定目标过滤器的生命周期,若绑定则执行delegate的初始化方法
        if (this.isTargetFilterLifecycle()) {
            delegate.init(this.getFilterConfig());
        }

        return delegate;
    }

    //执行委托的过滤器对象 delegate的过滤操作
    protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        delegate.doFilter(request, response, filterChain);
    }

    protected void destroyDelegate(Filter delegate) {
        if (this.isTargetFilterLifecycle()) {
            delegate.destroy();
        }

    }
}

     系统启动的时候会执行DelegatingFilterProxy的init方法(init是继承父类GenericFilterBean的

     方法);通过debug可以看到,我们在web.xml 配置的名称springSecurityFilterChain的Bean

     是FilterChainProxy;

      init方法的作用是:从IoC容器中获取 FilterChainProxy的实例对象,并赋值给

      DelegatingFilterProxy的delegate属性。

     在init方法中我们只需要关注 initFilterBean() 方法,该方法由各个子类实现,用来初始化不

     同的Filter对象。如下图所示:

              

     问题:web.xml中配置的名称为<filter-name> 的对象,如:名称为 springSecurityFilterChain

                 的对象(FilterChainProxy即的bean对象)是什么时候注入Spring IOC 容器的?

3、请求在SpringSecurity中的流转

      我们知道客户端请求需要经过很多个Web Filter拦截后才能到达Servlet,如下图所示:

              

      经过上边的分析知道,有一个我们在web.xml配置的拦截器 DelegatingFilterProxy 可以拦截

      客户端所有的请求,如下图所示:

              

      客户端请求会被 DelegatingFilterProxy的doFilter方法拦截,在 doFilter 方法中客户端请求会被

       委托给目标对象 FilterChainProxy 的 doFilter去拦截(请参考上边DelegatingFilterProxy 代

       码),流程如下图所示:

                

       

3.1、FilterChainProxy

         FilterChainProxy 过滤器链的代理对象
         增强过滤器链(具体处理请求的过滤器还不是FilterChainProxy )  根据客户端的请求匹配合

         适的过滤器链链来处理请求

         FilterChainProxy核心属性如下:

public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
	//过滤器链集合,保存多个过滤器链  一个过滤器链中包含的有多个过滤器
    private List<SecurityFilterChain> filterChains;
	//
    private FilterChainValidator filterChainValidator;
    private HttpFirewall firewall;
    
    //。。。。。。。
}

         FilterChainProxy 中真正处理用户用户请求的是doFilter方法(遇到Filter类第一眼就去找

          doFilter 方法)

          FilterChainProxy.doFilter 方法代码如下: 

//处理用户请求
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
		//最终都要执行 doFilterInternal 方法
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        } else {
            this.doFilterInternal(request, response, chain);
        }

    }

          doFilterInternal 方法:

/*
	    核心方法
		真正处理用户请求的方法
	*/
    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	    //处理请求和应答
        FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
		//获取当前请求的过滤器链
        List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
		//判断当前请求的过滤器链是否为空
        if (filters != null && filters.size() != 0) {
		    //将当前请求的过滤器链包装成 VirtualFilterChain,然后执行 VirtualFilterChain 的doFilter方法
			//VirtualFilterChain 是FilterChainProxy的内部类
            VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
            vfc.doFilter(fwRequest, fwResponse);
        } else {
		    //过滤器链为空,放过请求
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
            }

            fwRequest.reset();
			//放过请求
            chain.doFilter(fwRequest, fwResponse);
        }
    }

          在doFilter 方法中debug可以看到 getFilters() 方法返回的 拦截器有哪些(即当前请求需要

          执行的拦截器),如本人debug 时getFilters()返回的拦截器如下图所示:

                  

                   注意:Spring 版本不同,这里返回的拦截器可能也会有点区别。

          VirtualFilterChain 是FilterChainProxy的内部类,在VirtualFilterChain.doFilter 方法中遍历

           连接器集合,挨个执行每个拦截器。流程如下图所示:

                   

            VirtualFilterChain.doFilter 方法代码如下:

//遍历过滤器链,挨个执行每一个过滤器
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		    
		    //判断过滤器链 additionalFilters 中的过滤器是否执行完毕
			//currentPosition用来标识当前是执行过滤器链中的第几个过滤器
            if (this.currentPosition == this.size) {
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " reached end of additional filter chain; proceeding with original chain");
                }

                this.firewalledRequest.reset();
				//所有过滤器执行完成,则放过请求
                this.originalChain.doFilter(request, response);
            } else {
                ++this.currentPosition;
				//从集合中获取下一个要执行的过滤器
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
                }
                //执行下一个过滤器
                nextFilter.doFilter(request, response, this);
            }

        }

            所有拦截器执行完毕,请求就可以到达Servlet了

      


http://www.kler.cn/a/298062.html

相关文章:

  • 门店收银台支持商品类型-收银系统源码
  • Docker初始
  • 【Tools】Prompt Engineering简介
  • 数据结构---哈西表、算法
  • iOS18更新暂停卡住?iOS18升级失败解决办法分享
  • 招商银行信用卡中心编程练习题题解(全)
  • C#监测方法运行时间
  • go面试: GMP 模型为什么要有 P ?
  • 心得与体会
  • 小程序的面试题**
  • JavaScript高级——回调函数
  • 苏茵茵:以时尚之名,诠释品质生活
  • 深兰科技董事长陈海波出席《中马建交五十周年高级别经贸合作》
  • 如何确定 npm 依赖需要的 Node.js 版本?
  • pytorch torch.einsum函数介绍
  • vue缓存用法
  • 信息学奥赛初赛天天练-87-NOIP2014普及组-完善程序-矩阵、子矩阵、最大子矩阵和、前缀和、打擂台求最大值
  • Lombok失效:报错 找不到符号 Springboot项目
  • 【加密社】3分钟快速制作一个爬虫?不懂编程也没关系
  • YOLOv8改进 | 模块缝合 | C2f 融合RFAConv和CBAM注意力机制 【二次融合 小白必备】