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

news2024/11/22 23:26:17

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.coloradmin.cn/o/2118754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开关电源自动化测试有哪些流程和步骤?-天宇微纳

开关电源自动化测试的总体流程包括测试前的准备、测试过程中的具体步骤和测试后的数据处理三大部分。 以纳米软件ATECLOUD平台测试为例&#xff1a; ‌测试前的准备‌ ‌连接设备和仪器‌&#xff1a;通过LAN通讯总线、测试夹具以及其它线缆将需要测试的电源模块连接到纳米服…

【C语言】指针深入讲解(下)

目录 前言回调函数回调函数的概念回调函数的使用 qsort函数的使用和模拟实现qsort函数的介绍qsort函数的使用qsort函数模拟实现 前言 今天我们来学习指针最后一个知识点回调函数&#xff0c;这个知识点也很重要&#xff0c;希望大家能坚持学习下去。 没学习之前指针知识内容的…

k8s的配置管理

一、配置管理分为两种&#xff1a; 1. 加密配置&#xff1a;用来保存密码和token密钥对以及其它敏感的k8s资源。 2.应用配置&#xff1a;我们需要定制化的给应用进行配置&#xff0c;我们需要把定制好的配置文件同步到pod当中的容器。 二、加密配置 1.secret三种类型&#xf…

WPS取消首字母自动大写

WPS Office&#xff08;12.1.0.17827&#xff09; ① 点击文件&#xff0c;在文件中找到选项 ② 选择编辑&#xff0c;取消勾选

三国地理揭秘:为何北伐之路如此艰难,为何诸葛亮无法攻克陇右小城?

俗话说:天时不如地利&#xff0c;不是随便说说&#xff0c;诸葛亮六出祁山&#xff0c;连关中陇右的几座小城都攻不下来&#xff0c;行军山高路险&#xff0c;无法携带和建造攻城器械&#xff0c;是最难的&#xff0c;所以在汉中&#xff0c;无论从哪一方进攻&#xff0c;防守方…

计算机为啥选中二进制?

坊间传闻&#xff0c;当年&#xff0c;彷徨少年computer有幸读到东方奇书《道德经》中一段&#xff1a;“道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物。”忽然灵光乍现&#xff0c;做五体投地状。“啊门、主啊&#xff0c;我get到了&#xff0c;狗屁二生三…

「滚雪球学MyBatis」教程导航帖(已完结)

写在前面 我是bug菌&#xff0c;CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家&#xff0c;C站博客之星Top30&#xff0c;华为云2023年度十佳博主&#xff0c;掘金多年度人气作者Top40&#xff0c;掘金等各大社区平台签约作者&#xff0c;51CTO年度博…

python爬虫爬取淘宝商品比价||淘宝商品详情API接口

最近在学习北京理工大学的爬虫课程&#xff0c;其中一个实例是讲如何爬取淘宝商品信息&#xff0c;现整理如下&#xff1a; 功能描述&#xff1a;获取淘宝搜索页面的信息&#xff0c;提取其中的商品名称和价格 探讨&#xff1a;淘宝的搜索接口 翻页的处理 技术路线:requests‐…

随身WiFi大揭秘!9毛3000G?坑你没商量!博主亲测,教你如何避坑!

随身WiFi老坑人&#xff1f;流量收费坑&#xff1f;网速坑&#xff1f;今天本博主重金自费入购7款随身WiFi测评&#xff0c;这份避坑指南请您收好了&#xff01; 随身WiFi多吓人啊&#xff0c;一块9毛钱3000个G&#xff0c;还有的是5块9毛钱3000个G&#xff0c;确实有3000个G&…

同样数据源走RTMP播放延迟低还是RTSP低?

背景 在比较同一个数据源&#xff0c;是RTMP播放延迟低还是RTSP延迟低之前&#xff0c;我们先看看RTMP和RTSP的区别&#xff0c;我们知道&#xff0c;RTMP&#xff08;Real-Time Messaging Protocol&#xff09;和RTSP&#xff08;Real Time Streaming Protocol&#xff09;是…

京东鸿蒙上线前瞻——使用 Taro 打造高性能原生应用

背景 2024 年 1 月&#xff0c;京东正式启动鸿蒙原生应用开发&#xff0c;基于 HarmonyOS NEXT 的全场景、原生智能、原生安全等优势特性&#xff0c;为消费者打造更流畅、更智能、更安全的购物体验。同年 6 月&#xff0c;京东鸿蒙原生应用尝鲜版上架华为应用市场&#xff0c…

C++ 多态学习笔记(下)

开始新的学习之前&#xff0c;我们先通过一段涉及继承、多态的 代码来回忆、加深理解。 Animal作为基类&#xff0c;我们要给每种动物实例化出sound()的模块&#xff0c;因为Animal在实际意义上没什么好实例化的&#xff0c;所以设计成抽象类。 class Animal { public:virtua…

又考了两个Oracle认证:RAC和DataGuard,文末送资料

号主姚远目前已经拥有Oracle的认证超过20个了&#xff0c;最近又考了两个Oracle 19c的认证&#xff0c;是RAC和DataGuard。其实内容和12c没有太大的区别&#xff0c;但题目依然很难&#xff0c;很多选项模拟两可&#xff0c;需要对相关概念非常清楚才能通过考试。姚远的运气不错…

Linux网络:应用层协议http/https

认识URL URL是我们平时说的网址 eg&#xff1a;http常见的URL http://user:passwww.example.jp:80/dir/index.htm?uid1#ch1 注意&#xff1a; 服务器地址就是域名&#xff0c;相当于服务器ip地址 像http服务绑定80端口号&#xff0c;https服务绑定443端口。ssh服务端口绑定…

EasyCVR无法启动并报错“error while loading shared libraries”,如何解决?

安防监控/视频汇聚平台EasyCVR视频管理系统以其强大的拓展性、灵活的部署方式、高性能的视频能力和智能化的分析能力&#xff0c;为各行各业的视频监控需求提供了优秀的解决方案。通过简单的配置和操作&#xff0c;用户可以轻松地进行远程视频监控、存储和查看&#xff0c;满足…

Python操作ES集群API(增删改查等)

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 学习B站博主教程笔记&#xff1a; 最新版适合自学的ElasticStack全套视频&#xff08;Elk零基础入门到精通教程&#xff09;Linux运维必备—Elastic…

Vue组件:插槽的使用

在实际开发中&#xff0c;子组件往往只提供基本的交互功能&#xff0c;而内容是有父组件来提供的。为此&#xff0c;Vue.js 提供了一种混合父组件内容和子组件模板的方式&#xff0c;这种方式称为内容分发。 1、基本用法 Vue.js 参照当前 Web Components 规范草案实现了一套内…

和 InternLM 解锁“谁是卧底”新玩法

本文来自社区投稿&#xff0c;作者LangGPT联合发起人、东北大学在读博士生王明 在大模型技术日益普及的今天&#xff0c;AI 的应用已经渗透到各个领域&#xff0c;带来了无数创新和乐趣。今天&#xff0c;我们将一起探索如何搭建一个 AI 版的“谁是卧底”游戏。通过 InternStud…

【Unity基础】Input中GetAxis和GetAxisRaw的区别

一句话描述&#xff1a;GetAxis使用了平滑过渡&#xff0c;而GetAxisRaw是直接改变。 在Unity中&#xff0c;Input.GetAxisRaw 和 Input.GetAxis 都用于获取输入设备的轴向输入&#xff08;例如键盘、鼠标或手柄的摇杆&#xff09;&#xff0c;但它们的工作方式和返回值有细微…

GPU 服务器性能评估:多维度深度探索

在深度学习的浩瀚宇宙中&#xff0c;GPU 服务器犹如一颗璀璨的星辰&#xff0c;以其无与伦比的计算能力和效率引领着技术进步的浪潮。为了充分挖掘这一强大工具的潜力&#xff0c;我们需深入探寻其性能评估的奥秘&#xff0c;这不仅仅是对单一指标的简单堆砌&#xff0c;而是从…