(六)SpirngSecurity创建springSecurityFilterChain的详细过程
标签: (六)SpirngSecurity创建springSecurityFilterChain的详细过程 spring boot博客 51CTO博客
2023-05-06 18:24:05 233浏览
通过上篇文章中的debug分析,目前我们已经知道了以下几个概念:
1、DelegatingFilterProxy
2、springSecurityFilterChain
3、SpringBoot是如何找到SpringSecurity的配置入口SecurityAutoConfiguration
4、SpringSecurity处理请求的基本流程
另外在自动配置类SecurityAutoConfiguration中,引入了配置类WebSecurityEnablerConfiguration


还是这个@EnableWebSecurity注解,上篇文章中只是简单的介绍了一下其引入的WebSecurityConfiguration配置类,并且在WebSecurityConfiguration配置类中看到了springSecurityFilterChain的bean定义,下面来看下@EnableGlobalAuthentication注解中有什么内容

可以@EnableGlobalAuthentication注解的主要工作其实又是由AuthenticationConfiguration配置类来完成的

可以看到AuthenticationConfiguration又导入了另一个配置类ObjectPostProcessorConfiguration


下面先来简单看下这两个配置类中都有哪些bean,混个眼熟。
一、ObjectPostProcessorConfiguration
post-processor在编程中很常见,可以理解为一个后置处理器。当然前置处理器就是pre-processor。这个配置类中只配置了一个bean:

自动传入的bean是一个AutowireCapableBeanFactory类型的BeanFactory,顾名思义就是具有自动装配能力的beanFactory。
ObjectPostProcessorConfiguration中只是通过AutowireCapableBeanFactory生成了一个名为objectPostProcessor的bean,并没有其他逻辑。

二、AuthenticationConfiguration
authenticationConfiguration配置类中存在四个bean,如下图所示
1、AuthenticationManagerBuilder

2、GlobalAuthenticationConfigurerAdapter

3、InitializeUserDetailsBeanManagerConfigurer

4、InitializeAuthenticationProviderBeanManagerConfigurer

列出上面的bean都是为了混个脸熟,先只需要知道这些bean的存在就行,后面会用到。通过上一篇的debug分析,我们可以知道SpringSecurity框架的入口就是springSecurityFilterChain的doFilter方法。要是想熟悉整个验证流程,则最好从这个方法开始一步步深入。不过在查看doFilter方法之前,先来看下springSecurityFilterChain的初始化细节。
三、springSecurityFilterChain的bean初始化

1、webSecurityConfigurers
初始化bean的第一步用到了这个webSecurityConfigurers的类,在当前类中搜索这个变量:

可以发现这是个SecurityConfigurer<Filter, WebSecurity>类型的List,来看下它是在哪初始化的。在当前配置类中发现了下面这个方法,@Autowired注解自动注入了两个bean,分别是objectPostProcessor和webSecurityConfigurers。objectPostProcessor在本文最开始的时候我们已经看到了,webSecurityConfigurers是在初始化的呢?

不过我们可以发现参数之前有个@Value注解:

这个注解的意思是,webSecurityConfigurers是通过调用beanName为autowiredWebSecurityConfigurersIgnoreParents的bean的getWebSecurityConfigurers()获得的。而这个bean恰好在当前配置类中有定义,来了解下其内部逻辑

我们可以看到getWebSecurityConfigurers方法确实存在

通过代码我们可以得知,webSecurityConfigurers其实就是spring上下文中所有WebSecurityConfigurer类型的bean的集合。

看下他的继承结构,我们发现WebSecurityConfigurerAdapter和DefaultConfigurerAdapter是不是在哪见过,bingo~在前面的配置类中确实看到了这两个类


所以说,这个DefaultConfigurerAdapter就是SpringSecurity提供的默认配置类,我们debug来验证一下,我们推测的是正确的。

所以webSecurityConfigurers默认就是List<>{ DefaultConfigurerAdapter },不过通过源码的阅读我们可以得到一个结论,如果我们继承webSecurityConfigurer接口,定义自己的配置类并且注册成bean,也会被SpringSecurity加载。
2、webSecurity
webSecurity的初始化也是在注入webSecurityConfigurers时进行的,其中用到了objectPostProcessor的postProcess方法。


由上面源码得知postProcess的作用其实就是将webSecurity对象创建成一个bean注册到spring上下文中。目前已经可以得知这个objectPostProcessor是干什么的了,就是用来动态生成bean的,而且动态生成bean的同时还能够提供bean生命周期中post相关的操作的支持。读到这可以得出一个结论,webSecurity类型的bean是在处理webSecurityConfigurers时动态生成的。

webSecurityConfigurers的下一步处理是@Order注解,上面这段代码的意思说明配置类的@Order注解有两个作用,一是用来排序,二是用来表示唯一性。

另外根据其排序函数compare,可以得出排序规则为:如果两个元素只有一个继承了PriorityOrdered接口,则这个元素排在后面。如果都继承了或者都没继承,则order小的排在前面。如果没有注解@Order,则返回


排序完之后,会执行一个apply操作。这个方法是继承于抽象类AbstractConfiguredSecurityBuilder,webSecurity继承了这个抽象类。




上面这段代码很有意思,我也是看了几遍并且结合上下文才看出来他的意图。即AbstractConfiguredSecurityBuilder对象中有个HashMap类型的变量configurers,键为配置类的Class对象,值为一个List<SecurityConfigurer>类型的list。并且根据一个boolean类型的allowConfigurersOfSameType变量的控制,当其为true时允许同类型的配置共同存在,即list中元素可已有多个,当其为false时,则每次新添加的配置类都会覆盖掉list中已经存在的配置类。结合上面的webSecurityConfigurers的排序功能和webSecurity对象中allowConfigurersOfSameType变量为false,可以得出一个结论:
如果webSecurityConfigurers中有多个元素,则排序后添加到webSecurity中时,只有最后一个会保留。
3、springSecurityFilterChain的创建
webSecurityConfigurers的加载和处理,webSecurity实例的创建在上面已经分析了。最后一步就是通过webSecurity对象创建springSecurityFilterChain了,build方法也是继承于AbstractSecurityBuilder抽象类的。

O泛型在这里是Filter类型,想也想得到,毕竟是springSecurityFilterChian是一个Filter

从上面代码来看,一个builder只允许build一次。

build方法是抽象方法,很典型的一个模板方法设计模式。抽象父类定义流程骨架,不同的子类提供不同的实现方式。不过在这里是由抽象子类AbstractConfiguredSecurityBuilder实现了doBuild方法,并且引入了新的流程骨架。抽象子类AbstractConfiguredSecurityBuilder相比较于其抽象父类AbstractSecurityBuilder,额外多提供了配置信息相关的功能。

从上面的代码来看,doBuild过程是具有生命周期概念的。另外beforeInit、beforeConfigure这两个函数是钩子函数,是为了留给子类进行功能扩展的,所以在这里为空。整个doBuild过程分为三个阶段,四个状态。分别对应是三个方法和四个枚举值。依次来debug一下这几个方法中SpringSecurity做了哪些工作。
3.1 init方法


init方法中的getConfigurers取出的就是前面提到过的

中的所有List的集合。而且通过前面的分析也已经知道了,只存在一个DefaultConfigurerAdapter实例。由于init方法是抽象父类中定义的,所以可以被子类重载,为了了解其中的细节还是跟进去看一下。


从源码中可知DefaultConfigurerAdapter的init方法主要是使用了一个HttpSecurity对象给WebSecurity对象添加了一个SecurityFilterChainBuilder对象。首先来看一下getHttp方法(过程真的是很繁琐,不过耐心看下来还是有很多收获的):


objectPostProcessor已经看到很多次了,动态的将eventPublisher注册成bean到Spring上下文中。后面的localConfigureAuthenticationBldr的初始化代码也在当前配置类中,设置了一个LazyPasswordEncoder类型的属性,从名字上来看像一个加密的工具类,暂时不知道什么用途。另外还同时设置了内部属性authenticationBuilder的初始值。authenticationBuilder和localConfigureAuthenticationBldr唯一的不同点就在后者重写了一个eraseCredentials方法。

authenticationEventPublisher只是一个set函数而已

然后通过下面这个函数返回了一个AuthenticationManager对象,在上篇文章中我们有简单提到过,在使用用户名密码登录时,用户名和密码会被封装成一个UsernamePasswordAuthenticationToken的对象然后调用authenticationManager的authenticate方法去完成登录验证,所以这里就是authenticationManager对象的创建过程了:


从代码来看,在同一个WebSecurityConfigurerAdapter(DefaultConfigurerAdapter的抽象父类)对象中authenticationManger只需要初始化一次。来看下authenticationManager的创建过程。首先是通过上面介绍过的localConfigureAuthenticationBldr对象进行一个配置操作,如下将tag标记为true(应该说是默认为true),这个tag有什么用后面会看到

然后就是调用了authenticationConfiguration配置类的getAuthenticationManger方法。

authenticationConfiguration配置类在文章开头就已经提到了,我们直接看他的这个方法:


因为配置类是全局唯一的,所以可以得出authenticationManager不仅仅是WebSecurityConfigurerAdapter对象中唯一的,而且在AuthenticationConfiguration配置类中也有一个实例,当第一次被加载的时候才进行初始化。继续阅读源码可以发现创建了一个authBuilder对象,在前面WebSecurityConfigurerAdapter类中已经存在了一个localConfigureAuthenticationBldr,同为AuthenticationManagerBuilder类型,回过头去再看下WebSecurityConfigurerAdapter中的那个tag属性disableLocalConfigureAuthenticationBldr,现在应该清楚了:AuthenticationManager是通过AuthenticationManagerBuilder对象创建的,WebSecurityConfigurerAdapter和AuthenticationConfiguration中都有自己的AuthenticationManagerBuilder对象。默认情况下所有的WebSecurityConfigurerAdapter中的AuthenticationManager对象都是使用配置类中定义的AuthenticationManagerBuilder创建的,但是如果我们想定义自己的WebSecurityConfigurerAdapter,并且自定义AuthenticationManger对象,只需要将disableLocalConfigureAuthenticationBldr设置为false并且构造自己的localConfigureAuthenticationBldr即可。这算是SpringSecurity留给我们的一个开放功能吧。

上面就是AuthenticationConfiguration配置类中提供的默认的AuthenticationManagerBuilder。由于@Configuration注解的配置类会被ConfigurationClassEnhancer代理,其内部被@Bean注解的方法默认会产生一个单例bean的实例我们都知道。但是如果这个方法被显式调用,如上面的authBuilder的创建,由于代理类的作用,返回的还是初始化的时候创建的那个bean。从源码中可以了解到默认的authBuilder使用的是LazyPasswordEncode这个加密工具类。
全局配置初始化的地方也在AuthenticationConfiguration配置类中。

通过debug发现其存在三个bean,咦?是不是很熟悉。如果忘记了的话翻到文章最开始,是不是看到了几个AuthenticationConfiguration配置类中几个莫名其妙的bean

这里再贴一下:

通过源码可以看到这三个bean都继承了GlobalAuthenticationConfigurerAdapter抽象父类

apply方法前面也看到过,继承于AbstractConfiguredSecurityBuilder类。由此可见无论是构造Filter对象的WebSecurity还是构造AuthenticationManager对象的AuthenticationMangerBuilder,都集继承于AbstractConfiguredSecurityBuilder抽象类。使用灵活的泛型和继承给我们展示了一下什么叫编程的艺术。当然结果不用多说,就是将这三个全局配置类加载到了authBuilder对象中保存了起来。


由于其也是继承了AbstractConfiguredSecurityBuilder对象,免不了也要调用build方法创建对象,真是天道好轮回~不过为了掌握这个默认的AuthenticationManger到底有哪些功能,还是忍着头皮看一下doBuild方法。前面已经介绍道了,doBuild方法有三个函数四个状态。再次进入init方法。

不过这次我们需要debug三个configurer对象。之前的configurer还有印象吗?回想一下DefaultConfigurerAdapter,是不是想起来了。来依次看下这三个GlobalAuthenticationConfigurerAdapter的不同子类bean,都做了哪些事情:
首先是EnableGlobalAuthenticationAutowiredConfigurer:

只是从spring上下文中加载了被@EnableGlobalAuthentication注解的bean。那肯定是文章开头提到的WebSecurityEnablerConfiguration配置类了。
然后是InitializeAuthenticationProviderBeanManagerConfigurer:


这是又创建了InitializeUserDetailsManagerConfigurer对象,这个类继承了GlobalAuthenticationConfigurerAdapter并且InitializeAuthenticationProviderBeanManagerConfigurer的内部类,然后注册到authBuilder对象中去了。不过这里需要注意的是,前面已经提到了doBuild具有生命周期:此时已经是INITIALIZING状态了,所以这个InitializeUserDetailsManagerConfigurer对象同时添加到了configurersAddedInInitializing集合和configurers哈希表中。



最后一个全局配置类是InitializeUserDetailsBeanManagerConfigurer:

然鹅。。InitializeUserDetailsBeanManagerConfigurer也有一个名为InitializeUserDetailsManagerConfigurer的内部类,同样继承了GlobalAuthenticationConfigurerAdapter,所以重复上面的动作。此时authBuilder的configurersAddedIninitializing中已经有两个配置类了。

所以接下来就是处理这两个配置类,两个InitializeUserDetailsManagerConfigurer

不过这两个配置类并没有重写init方法,所以并没有处理逻辑~开心!
authBuilder的init方法完成后,此时状态被置位BuildState.CONFIGURING,然后开始configure方法。

要死了,根据前面的结果,现在configures中应该有五个类。。没办法一个个的看吧。

不过幸好只有最后添加的那两个InitializeUserDetailsManagerConfigurer重写了configurer方法,来看下他们做了哪些工作,首先是第一个:


代码很简单,从spring的上下文中取出所有AuthenticationProvider类型的bean中的第一个,设置到authBuilder中。下面继续看第二个InitializeUserDetailsManagerConfigurer:


首先是从上下文中找到了所有UserDetailsService类型的bean中的第一个。如果没找到就返回。从Debug来看,这个默认的UserDetailService是InMemoryUserDetailsManager,从名字就能看出来这是个依靠内存来提供功能的类。然后是UserDetailsPasswordService这个bean也是InMemoryUserDetailsManager。最后创建了一个DaoAuthenticationProvider,并且设置为authBuilder的属性。tm的authBuilder的初始化工作终于完成了。最后就是创建环节了,真tm复杂。

还好创建过程很简单。由源码可知创建了一个ProviderManager对象,并且将前面默认生成的DaoAuthenticationProviders设为其属性。eventPublisher是前面生成的DefaultAuthenticationEventPublisher,然后继续看下postProcess后置处理函数做了哪些事情:

将这个ProviderManger动态生成了bean注册到Spring上下文中。
最后标记自己已经初始化了AuthenticationManager

到此为止,DefaultAuthenticationAdapter和AuthenticationConfiguration配置类中的tag都设置为了true。并且他们具有相同的AuthenticationManager实例——ProviderManager。回到getHttp()方法,回到主流程:
DefaultAuthenticationAdapter将系统默认生成的ProviderManager对象设置成自己的authenticationBuilder的parent对象:

createSharedObjects函数中只需要关注userDetailsService这一步


从Spring上下文中取出的globalAuthBuilder就是前面已经提到过的AuthenticationConfiguration配置类中生成了一个AuthenticationManagerBuilder的bean,DefaultAuthenticationAdapter中也创建了自己的localAuthenticationManagerBldr,UserDetailsServiceDelegator构造函数的参数就是这两个对象:


然后使用sharedObjects和设置了parent属性的authenticationBuilder构建了http对象:

然后就是一通设置,其中可以看到一个DefaultLoginPageConfigurer,这个就是提供默认登陆页面的配置,是动态生成的页面哦~

最后便设置了http对象的基本拦截工作

对于任意请求需要进行验证,使用默认提供的登陆页面,开启httpBasic验证等。最后便是使用这个生成的http对象添加到了WebSecurity对象中。


WebSecurity的init方法到此就结束了。真的是漫长。。不过幸好其configure方法没有作用,直接来看最后的performBuild方法是如何通过WebSecurity生成Filter的~
3.2、performBuild方法

这个securityFilterChainBuilder就是前面一大坨逻辑生成的HttpSecurity对象了。

不过HttpSecurity中还有一大坨配置加载,但是我们不需要去看了。主要加载逻辑我们都已经看完了。只需要最后在注意一下这个异步执行的postBuildAction.run,执行了

这个方法,向WebSecurity中设置了FilterSecurityInterceptor拦截器。至此,一个Filter类型的bean创建完成了。beanName就是springSecurityFilterChain。而且通过源码阅读 + debug分析的结合。我们可以得到以下的有用信息:
1、SpringSecurity上下文中默认的AuthenticationManger是ProviderManger
2、SpringSecurity上下文中默认的Provider是DaoAuthenticationProvider
3、SpringSecurity上下文中默认的UserService是InMemoryUserService
4、SpringSecurity在设置这几个对象时都是通过getBean的形式加载的,所以我们可以通过在Spring上下文中构造我们自己的bean去覆盖掉SpringSecurity默认的,来实现我们的扩展功能。
下篇文章我们通过分析一个详细的登陆过程,看一下ProviderManager的authenticate具体是如何进行登陆验证的。然后根据其登陆流程,采用上述所说的方法来扩展我们自己的登陆功能。
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论





