(八)从框架设计的角度来看springSecurityFilterChain
标签: (八)从框架设计的角度来看springSecurityFilterChain Java博客 51CTO博客
2023-04-13 18:24:16 243浏览
前面的文章中通过debug的形式已经了解了SpringSecurity从加载到启动再到处理请求的每一步。现在我们对SpringSecurity的整体结构有了基本的认识。但是在debug的过程中我们会注意到SpringSecurity在代码中应用了大量的泛型和继承,本篇文章就是来通过框架设计的角度来回顾下springSecurityFilterChain的创建过程。
一、SecurityConfigurer和SecurityBuilder简介

SecurityBuilder
从代码来看,build方法返回值为泛型类O。意味着继承SecurityBuilder接口,则意味着具有根据实现类内部属性的差异性来返回O类型的不同特性实例的功能,通常来说能够被标记为接口则表示在抽象的基础上还有强烈的语义色彩,比如这个创建实例的过程可能是有固定模板的。

SecurityConfigurer
SecurityConfigurer可以看做是SecurityBuilder的配置功能类。前面说到
SecurityBuilder可以“根据内部属性的差异性来返回O类型的不同特性实例”,则SecurityConfigurer就是用来提供SecurityBuilder的差异性。SecurityConfigurer的两个泛型,一个为SecurityBuilder的泛型类,一个为SecurityBuilder的实现类。
从方法的注释来看,之所以将SecurityConfigurer分为两部分,主要是为了使创建流程更规范。
init方法只用来处理共享变量。什么是共享变量?源码可以告诉我们答案:


意思就是某些SecurityBuilder中会创建自己的共享变量,然后builder中的共享变量其实是给所有与其相关的SecurityConfigurer共享的。通过SecurityConfigurer的init方法,用来给SecurityBuilder中的共享变量进行一些操作。来看一个SecurityConfigurer的具体实现类就能够明白了

LogoutConfigurer的init方法
configure方法是直接改变SecurityBuilder本身,比如给其设置必要的属性。还是来看一个实际的实现:

二、SecurityBuilder和SecurityConfigurer的实际实现分析
上面介绍了SecurityBuilder和SecurityConfigurer的基本内容,但是其实还是比较懵的,所以下面来看下框架中是如何将SecurityBuilder和SecurityConfigurer结合到一起的。
由于SpringSecurity框架中的c位是springSecurityFilterChain的,所以就看下他的相关builder和configurer

1、SecurityConfigurer
首先是WebSecurityConfigurer,从源码和注释来看,这个接口只是起到一个标记作用,能够让@EnableWebSecurity注解自动加载实现了该接口的SecurityConfigurer

WebSecurityConfigurer的唯一子类是WebSecurityConfigurerAdapter,

从注释来看它主要就是作为WebSecurityConfigurer的基本实现,子类可以通过重写父类方法的形式来自定义行为。
SecurityConfigurer这一列没什么需要继续探究的了,相比较于SecurityConfigurer家族,builder家族要复杂的多,毕竟SecurityConfigurer只是用来提供配置。
2、SecurityBuilder
WebSecurity实现了SecurityBuilder<Filter>接口。不过除了实现SecurityBuilder<Filter>接口,其还实现了AbstractConfiguredSecurityBuilder<Filter, WebSecurity>抽象类:

AbstractConfiguredSecurityBuilder的抽象父类AbstractSecurityBuilder<Filter>也继承于SecurityBuilder<Filter>接口,讲道理这种集成方式还是第一次见到,继承的抽象类和自己实现了相同的接口。

注释或许能够给我们一些灵感:
首先是SecurityBuilder的直接抽象子类AbstractSecurityBuilder,通过注释可以了解到,主要是用来限制O类型的对象能且仅能被build一次。

其内部使用了一个原子变量来标记是否已经build:


AbstractConfiguredSecurityBuilder继承了抽象类AbstractSecurityBuilder,根据继承的性质其也具有能且仅能build一次的性质:

第一段注释的大概意思是,AbstractConfiguredSecurityBuilder允许SecurityConfigurer被applied(意思就是提供一个apply方法)。通过其对外提供apply方法使得我们可以掌控SecurityBuilder的build行为。将build过程根据构建目标来划分成不同的SecurityConfigurer比创建多个SecurityBuilder要好。按照我的理解,SecurityBuilder最终的目标就是build出一个对象,但是如果我们需要build出不同特性的结果,相比较于定义多个SecurityBuilder,不如只定义一个SecurityBuilder并且将特性相关的内容放到SecurityConfigurer中。根据apply方法来选择使用不同的SecurityConfigurer,从而使最后build出来的相同类型的实例具有不同的行为。
第二段注释举了一个例子,大概意思是:对于创建DelegatingFilterProxy的SecurityBuilder,通过SecurityConfigurer可以给SecurityBuilder塞入某些必要的filter,比如session管理的fiter,登陆的filter,授权的filter等。
先来看下apply方法

根据Configurer的实现类的不同,分为两个版本。相同点为都包含了add方法,所以这个是apply方法的主要作用。
由注释可知,add方法主要是为了确保某些必要的SecurityConfigurer的init方法被调用,这些必要的configurer类被保存在AbstractConfiguredSecurityBuilder的configurers和configurersAddedInIntializing变量中。

现在我们再回过头去看springSecurityFilterChain的创建过程,会发现清晰很多:

上面是创建SpringSecurityFilterBean的源码,在此之前我们已经看过很多遍了。这次从设计模式的角度去看下。
可以看到SpringSecurityFitlerChain是由webSecurity对象创建的。

从源码可以看到其继承了AbstractConfiguredSecurityBuilder,所以WebSecurity是一个SecurityBuilder,并且具有能且仅能build一次的约束和可以根据不同的SecurityConfigurer创建出不同特性的springSecurityFilterChain的能力。

WebSecurity的bean是在上面一段代码中创建的,创建过程主要围绕着SecurityConfigurer来进行的:
1、new一个WebSecurity实例
2、将系统默认的SecurityConfigurer排序并且验证唯一性
3、调用apply方法加载需要用到的SecurityConfigurer
系统默认的SecurityConfigurer在前几篇文章中已经分析过了,加载的是Spring上下文中所有WebSecurityConfigurer的bean


系统中只提供了一个DefaultConfigurerAdapter

WebSecurity中apply的Configurer就是这个bean。经过apply之后DefaultConfiguerAdapter被保存到了WebSecurity的configurers变量中。前面已经介绍到,这些被apply的configurer是为了调用他们的init方法,最后来看下创建过程,即WebSecurity的build函数

WebSecurity没有重写build方法,直接继承于AbstractSecurityBuilder,只能build一次,否则会抛出异常:dobuild方法在AbstractSecurityBuilder中定义,在子类AbstractConfiguredSecurityBuilder中提供实现,提供了build的具体过程


AbstractConfiguredSecurityBuilder中实现的doBuild方法,使用以及被apply的SecurityConfigurer来build对象,并且将build过程分为了三个过程:
1、init

依次调用所有必要的SecurityConfigurer的init方法。
2、configure

依次调用所有的必要的SecurityConfigurer的configurer方法。
3、build
执行performBuild方法,使用init和configure方法中处理好的变量build真正的对象。

构造过程中用到的securityFilterChainBuilders,ignoredRequests,httpFirewall等属性都是在configure阶段设置的。
三、WebSecurity和HttpSecurity
细心的朋友可能会问了,创建过程中并没有发现Filter相关的创建配置?而且WebSecurity最终创建的结果其实是一个filterChain的集合,但是beanName为springSecurityFilterChain。那么具体的filterChain又是谁创建的?答案就是HttpSecurity:



通过分析我们已经知道,SecurityBuilder的所有属性都是SecurityConfigurer中的configurer方法设置的,而对共享变量的改变是通过SecurityConfigurer的init方法执行的,而且系统默认只提供了DefaultConfigurerAdapter这一个配置类,所以addSecurityFilterChain方法就是由DefaultConfigurerAdapter的init方法执行的,init方法继承于父类WebSecurityConfigurerAdapter:

1、HttpSecurity基本定义

从源码可以得知HttpSecurity也是一个SecurityBuilder,创建的对象类型为DefaultSecurityFilterChain。


通过接口定义即可看出,filterChain主要提供了两个功能:
1、url匹配
2、管理filter
在springSecurityFilterChain中可以创建一个或者多个SecurityFilterChain,并且根据SecurityFilterChain提供的requestMatcher来决定使用哪个SecurityFilterChain中的filter来处理当前请求,相当于于一个代理。
2、HttpSecurity相关的SecurityConfigurer
现在我们可以知道的是springSecurityFilterChain只是一堆SecurityFilterChain的代理。而处理请求逻辑的filter都是由SecurityFilterChain加载和提供的。要想了解默认的filter是如何加载的以及后面我们如何自定义filter,必须了解这个过程。不过在此之前我们需要先思考一个问题,作为一个SecurityBuilder,要想完成一些工作,必须结合SecurityConfigurer。HttpSecurity的SecurityConfigurer是从哪里获得的呢?
系统默认提供的HttpSecurity会带给我们答案:

系统默认提供的HttpSecurity对象是从getHttp方法获得的,所以我们的答案就在代码里。


从源码中可以看出HttpSecurity对象中提供了很多的方法,比如logout方法:



答案貌似已经揭开了,HttpSecurity提供了许多的默认方法,每一个默认方法都会给HttpSecurity对象中添加一个SecurityConfigurer对象。
比如logout方法就会给HttpSecurity对象添加了LogoutConfigurer,HttpSecurity提供的所有配置类如下,他们都继承于AbstractHttpConfigurer抽象类:

3、filter的创建
通过前面的介绍我们已经知道SecurityBuilder会在init阶段和configurer分别调用SecurityConfigurer的init方法和configurer方法。所以以LogoutConfigurer为例,来看下他是如何提供注销功能的。

init方法

configure方法

答案就是在configure方法中给HttpSecurity对象添加的~
所以如果我们想添加或者删除某些filter只需要重写getHttp方法,通过调用HttpSecurity对象的不同方法来选择启用某些filter即可。
四、总结
SpringSecurity框架中最核心的配置就是SecurityBuilder和SecurityConfigurer,
SecurityBuilder只提供创建的基本步骤,SecurityConfigurer提供创建的元数据。
SpringSecurity中最主要的三个类:HttpSecurity、WebSecurity、AuthenticationManger都是通过这个设计来创建的。
现在我们应该已经完全掌握SpringSecurityFilterChain的创建流程以及请求处理流程了,而且也应该知道该如何去修改系统默认的功能。下一节我们就来自定义我们自己的登陆流程。
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论
