Spring Security框架配置运行流程完整分析
Spring Security配置流程剖析
第一步:收集WebSecurityConfigurer配置类
@EnableWebSecurity注解
引入了WebSecurityConfiguration 配置类
WebSecurityConfiguration
定义了AutowiredWebSecurityConfigurersIgnoreParents
这个bean,这个bean会寻找容器中的 WebSecurityConfigurer 接口的配置类- 我们自定义的 MySecurityConfig 正好继承自
WebSecurityConfigurerAdapter
,实现了WebSecurityConfigurer
接口 WebSecurityConfiguration
的 setFilterChainProxySecurityConfigurer 方法,使用了@Autowired注解和EL表达式注入了AutowiredWebSecurityConfigurersIgnoreParents
,这个bean的 getWebSecurityConfigurers 方法返回的所有 WebSecurityConfigurer 的配置类,并且当容器中有多个WebSecurityConfigurer时,会对这些webSecurityConfigurer配置类进行排序
第二步:创建 WebSecurity
-
在 WebSecurityConfiguration 的 setFilterChainProxySecurityConfigurer 方法里,创建了 WebSecurity 实例,并且调用 webSecurity.apply(webSecurityConfigurer),apply了所有的 WebSecurityConfigurer 的配置类(注意此时只是添加了webSecurityConfigurer配置类)
-
然后,调用 webSecurity 的 build方法 ,开启了 WebSecurity的
beforeInit -> init -> beforeConfigure -> configure-> performBuild
一系列流程,下面就详细说下这所谓的一系列流程。
第三步:调用 WebSecurity 的 build 方法来开启整个流程,创建 HttpSecurity实例
- WebSecurityConfiguration 使用了@Bean 标注的 springSecurityFilterChain()方法,去使用 WebSecurity 实例的 build 方法(是为了去创建
FilterChainProxy
) - 在这个WebSecurity 的 build方法里,会来到 AbstractConfiguredSecurityBuilder 的 doBuild 流程,在这个流程里,开始了
beforeInit -> init -> beforeConfigure -> configure-> performBuild
一系列步骤。 - 在 WebSecurity的 init流程里,==会回调所有
WebSecurityConfigurer 配置类的 init 方法
(这里非常关键)!!!因此 WebSecurityConfigurerAdapter 的 init(final WebSecurity web) 方法被执行,在这个 init 的方法里,会去调用 getHttp() 去获取了HttpSecurity实例
,并且把 HttpSecurity 实例作为SecurityFilterChainBuilder
添加到 SecurityFilterChainBuilders 属性中。
第四步:创建AuthenticationManager实例
上面提到的 getHttp() 也是比较复杂的,下面列出其中的关键步骤,方便理解
1、获取 AuthenticationManagerBuilder
WebSecurityConfigurerAdapter 默认会交给 注入的
AuthenticationConfiguration
调用它的 getAuthenticationManager() 方法获取AuthenticationManager。(还有个隐藏的细节是:既然WebSecurityConfigurerAdapter能自动注入AuthenticationConfiguration,那么说明肯定是AuthenticationConfiguration)
1、AuthenticationConfiguration会先创建 DefaultPasswordEncoderAuthenticationManagerBuilder 得到 authBuilder ,并且里面会使用 LazyPasswordEncoder 从容器中获取 PasswordEncoder,
2、然后,AuthenticationConfiguration 会使用@Autowired注入所有的 GlobalAuthenticationConfigurerAdapter,遍历它们并且authBuilder.apply(GlobalAuthenticationConfigurerAdapter)(跟前面一样就只是添加了配置类),这些配置类也都是在 AuthenticationConfiguration 类中使用@Bean配置的。其中包括:
1、EnableGlobalAuthenticationAutowiredConfigurer、
2、InitializeAuthenticationProviderBeanManagerConfigurer、
3、InitializeUserDetailsBeanManagerConfigurer
3、最后,再调用 authBuilder.build() 构建出一个 AuthenticationManager。这个 build()方法也会触发 AbstractConfiguredSecurityBuilder 的 doBuild 流程。 即:beforeInit -> init -> beforeConfigure -> configure-> performBuild
一系列步骤,步骤跟前面几乎一样,重点是应用的这3个配置类。也会调用这些配置类的 init 方法 和 configure 方法。
-
在 init 中:
1、EnableGlobalAuthenticationAutowiredConfigurer -> 触发所有加了@EnableGlobalAuthentication注解的bean初始化
2、InitializeAuthenticationProviderBeanManagerConfigurer -> 在authBuilder中添加 InitializeUserDetailsManagerConfigurer(内部类)
3、InitializeUserDetailsBeanManagerConfigurer -> 在authBuilder中添加 InitializeUserDetailsManagerConfigurer(内部类) -
在 configure 中:
1、
InitializeAuthenticationProviderBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer
尝试从spring容器中获取 AuthenticationProvider 实例,如果能获取到,则设置给 authBuilder(相当于是给用户一个机会去提供自定义AuthenticationProvider实现)
2、InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer
尝试从spring容器中获取 UserDetailsService 实例,如果能获取到 UserDetailsService 实例,才会继续下面的步骤。尝试从容器中获取 PasswordEncoder实例、UserDetailsPasswordService实例,创建DaoAuthenticationProvider
对象,并将获取的 UserDetailsService 实例,UserDetailsPasswordService实例设置进去,将此 DaoAuthenticationProvider 对象 设置给 authBuilder(如果用户没有提供自定义AuthenticationProvider实现,默认用此实现) -
在 performBuild 中:
AuthenticationManagerBuilder 在 performBuild 方法中,先创建了 ProviderManager,并且传入了前面获取的 DaoAuthenticationProvider 作为 AuthenticationProvider实现。然后,就是设置 providerManager 的 eraseCredentialsAfterAuthentication、setAuthenticationEventPublisher 属性。最后还是会使用 objectPostProcessor 来对 providerManager 做后置处理。到这里为止,AuthenticationManager 已经被创建出来了,继续回到 主线WebSecurityConfigurerAdapter 的authenticationManager() 方法
创建出来 AuthenticationManager 后,设置给 WebSecurityConfigurerAdapter 的 authenticationManager 属性。同时,将它也设置给 WebSecurityConfigurerAdapter 的 authenticationBuilder 属性中作为 parentAuthenticationManager
2、创建 HttpSecurity
WebSecurityConfigurerAdapter在getHttp()方法中 创建了 HttpSecurity
,并传入了 authenticationBuilder 属性
、objectPostProcessor、sharedObjects。这时我们注意到,authenticationBuilder被放到了sharedObjects属性中(并且key为AuthenticationManagerBuilder.class)
3、配置 HttpSecurity
-
在创建HttpSecurity之后,会对httpSecurity实例进行默认设置:
http .csrf().and() .addFilter(new WebAsyncManagerIntegrationFilter()) .exceptionHandling().and() .headers().and() .sessionManagement().and() .securityContext().and() .requestCache().and() .anonymous().and() .servletApi().and() .apply(new DefaultLoginPageConfigurer<>()).and() .logout();
也就是说上面这些设置,都是security框架默认配置给HttpSecurity的,即使你不配置,它也这样玩。
-
然后,使用SpringFactoriesLoader加载所有jar包下的META-INF/spring.factories文件中配置AbstractHttpConfigurer,实例化它们,然后调用http.apply(configurer) (跟前面一样就只是添加了配置类)。最后,手动调用http.configure(HttpSecurity http),里面有默认的实现,默认如下:
http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic();
也就是说,这个也是security框架默认配置给HttpSecurity的,在配置完前面之后,它又来配置HttpSecurity了。但是我们一般会重写这个方法。换句话说,如果我们不重写这个方法,那么就会用上面的这些默认配置。而重写该方法后,我们可以利用security提供的众多配置类,往HttpSecurity中加需要的配置类,现在我们应该能明白,我们重写这个configure(HttpSecurity)是干嘛的了以及它的执行时机了。注意,现在我们只是在WebSecurity被WebSecurityConfigurerAdapter的init方法初始化时,创建了HttpSecurity,并且往HttpSecurity中应用了配置器,但是这些配置器还没有起作用,这些配置器的顺序不是固定的,但security已经在代码中为我们写了,这应该就是建议的顺序,如果我们不想要这个顺序,可以自行修改,但前提是必须了解它们的工作原理。
第五步:配置 WebSecurity
WebSecurity 的 init 方法走完后, 按照前面提到的流程,开始走 WebSecurity 的 configure 方法。在 WebSecurity的 init流程里,会回调所有 WebSecurityConfigurer 配置类的 configure 方法,这里我们其实可以重写 WebSecurityConfigurer 配置类的 configure 方法,也就是说,我们现在已经知道了我们重写配置类的configure(WebSecurity)方法的执行时机了,他就是走完WebSecurity的init方法执行后被执行。
第六步:webSecurity 构建 FilterChainProxy 作为Filter
现在到了 WebSecurity的performBuild()
方法,这是 WebSecurity 要完成使命的地方了,前面配置了那么多,终于要看看security到底要干嘛了。
- 遍历 WebSecurity 中的 securityFilterChainBuilders 属性, 分别调用它们的 build 方法, 构建出一条条的 SecurityFilterChain 过滤器链。再提醒一遍,
前面security不辞辛苦的一直在配置HttpSecurity,正是 SecurityFilterChainBuilder 类型的
,它就是用来构建出这样的 过滤器链的。现在我们能明白的配置HttpSecurity的目的了。- 但是先别急,值得注意的是:这里我们给HttpSecurity的相关配置就开始要生效了,因为这里触发了HttpSecurity#build方法,开启了
beforeInit -> init -> beforeConfigure -> configure-> performBuild
一系列的流程。相当于之前我们配置在HttpSecurity中的配置类,要在这一系列的流程中要生效了,并且会触发HttpSecurity类中定义的相关方法,所以我们这个时候,去关注HttpSecurity中的相关方法,比如HttpSecurity#beforeConfigure()方法中就设置了AuthenticationManager到sharedObjects中,而且是从sharedObjects中获取到前面提到的AuthenticationManagerBuilder,然后调用这个builder的build方法构建出来的
。这样认证管理器的获取流程就完成了。并且HttpSecurity在这一系列流程中,回调这些配置类的init和configure等等(也有其他的比如beforeConfigure)这些方法,去根据HttpSecurity中的相关配置(比如:获取相关配置类,sharedObjects什么的)创建出Filter,添加到HttpSecurity中,这样HttpSecurity中就维护了一个过滤器的列表。不过,我们还要注意一点,我们在前面已经知道HttpSecurity会开启一系列流程,在HttpSecurity的performBuild流程中,会对filters属性中所添加的所有过滤器进行排序,这个排序规则就在FilterComparator中
(需要仔细体会一下,WebSecurity和HttpSecurity在这一系列流程中的相似之处,不得不说,这个security框架的设计真的是太牛逼了,代码重用达到了极致,而又如此灵活。但这会造成看源码的时候,会跳来跳去的现象,所以在AbstractConfiguredSecurityBuilder这个类中,跟源码的时候,一定要选择到“对应”的配置类才行)
- 但是先别急,值得注意的是:这里我们给HttpSecurity的相关配置就开始要生效了,因为这里触发了HttpSecurity#build方法,开启了
- 创建 FilterChainProxy ,并传入 SecurityFilterChain 过滤器链集合
- 执行postBuildAction.run(),这里面其实在 WebSecurityConfigurerAdapter 的 init 之中,就设置了 webSecurity的 postBuildAction属性,是将 HttpSecurity 的 sharedObjects 中的 FilterSecurityInterceptor 属性,设置给 WebSecurity
为什么必须叫springSecurityFilterChain这个名字?
至此,这个 FilterChainProxy 作为过滤器的 bean 已经构建完毕了,然后,它需要完成与 Web 的整合,这个时候,就利用了web框架中的 DelegatingFilterProxy。
WebSecurityConfiguration 的 springSecurityFilterChain() 返回的 bean 的名字就是"springSecurityFilterChain",这个方法里面构建出来的正是我们上面一直在说的 WebSecurity 的build()方法构建出来的filter。也就是说,这个springSecurityFilterChain()方法返回的filter将作为bean放置在spring容器当中,并且它的名字就是"springSecurityFilterChain"
。
而DelegatingFilterProxy配置在了web.xml中,那么DelegatingFilterProxy它是怎么把请求交给我们最终构建出来的Filter处理呢?没错,就是通过名字来找的。因为DelegatingFilterProxy可以拿到spring容器,那么就可以根据名字,从容器中拿到这个名字对应的bean,那么如果你把名字写错了,那就拿不到这个bean了。因此security框架创建的FilterProxy就可以被DelegatingFilterProxy所找到,进而交给FilterProxy处理了。
还有个问题可能会问:DelegatingFilterProxy为什么可以拿到spring容器?因为如果是之前的传统ssm整合,是有父子容器概念的,父容器在ContextLoaderListener中就被加载了,并且spring容器保存到了ServletContext域中。那么在Filter中肯定可以拿到ServletContext的,所以也就能拿到spring容器了,这个细节可以在DelegatingFilterProxy#findWebApplicationContext和WebApplicationContextUtils#getWebApplicationContext找到
相信有了上面的步骤相关的提示之后,在跟源码的时候,会有不少的帮助,下面再来对照源码,详述一下构建过程
核心要点
WebSecurity和HttpSecurity的构建过程
最后,我们再来简单的复盘一下整个过程:WebSecurityConfiguration在setFilterChainProxySecurityConfigurer(..)
方法中扫描容器中的所有WebSecurityConfigurer并排序,默认的WebSecurityConfigurerAdapter类上使用了@Order(100)默认排序值为100,然后创建WebSecurity,并将所有WebSecurityConfigurer添加到WebSecurity中,然后WebSecurityConfiguration在springSecurityFilterChain()
方法中调用WebSecurity的build方法开启整个一系列流程,开始对于走WebSecurity的beforeInit -> init -> beforeConfigure -> configure-> performBuild
,在此流程中的init方法中,就会回调每个WebSecurityConfigurerAdapter的init(WebSecurity)方法
,WebSecurityConfigurerAdapter的init(WebSecurity)方法中会先创建1个HttpSecurity,然后往HttpSecurity中添加配置类(注意,这里只是在给HttpSecurity添加配置器,并没有使用配置器去配置HttpSecurity,等到后面WebSecurity调用HttpSecurity的build方法时,这些配置类才会开始对HttpSecurity生效),WebSecurityConfigurerAdapter添加完默认的配置类之后,WebSecurityConfigurerAdapter又给子类1个机会去修改HttSecurity中的配置
,然后把HttpSecurity作为过滤器链构建器添加到WebSecurity的securityFilterChainBuilders属性中,其中,要注意的是每多1个WebSecurityConfigurerAdapter,回调每1个WebSecurityConfigurerAdapter的init(WebSecurity)方法时,就会多创建1个HttpSecurity,就往WebSecurity的securityFilterChainBuilders属性中多添加1个HttpSecurity
,并且每次构建好HttpSecurity后,并添加好默认的配置类之后,又会给WebSecurityConfigurerAdapter子类1个机会去修改自己创建的HttpSecurity
,注意不会影响到其它WebSecurityAdapter子类构建的HttpSecurity。然后现在进入WebSecurity的configure阶段,它会回调所有WebSecurityConfigurerAdapter的configure(WebSecurity)
方法。然后,现在进入WebSecurity的configure()阶段,它会回调所有WebSecurityConfigurerAdapter的configure(WebSecurity)方法
,此时,就相当于给了WebSecurityConfigurerAdapter子类1个机会去修改WebSecurity,比如往WebSecurity中添加忽略的路径,这些忽略的路径都会形成1条过滤器链,而过滤器链中一个过滤器都没有,只要路径匹配上了,就直接交给后面的Web容器的过滤器。 然后现在进入WebSecurity的performBuild阶段,在WebSecurity的performBuild()方法中,它会遍历所有的HttpSecurity,并且调用所有HttpSecurity的build()方法来创建过滤器链,每1个HttpSecurity都会创建1条过滤器链
,然后创建FilterChainProxy,把这些过滤器链添加到FilterChainProxy中。
对HttpSecurity的build()方法的调用会开启HttpSecurity的beforeInit -> init -> beforeConfigure -> configure-> performBuild
一系列流程,此时就会让HttpSecurity中所添加的配置器开始生效,这个时候,我们应当要注意HttpSecurity中所添加的所有配置器的顺序,这个添加顺序会影响到HttpSecurity最终构建的过滤器链中的过滤器相关的设置,后面的配置器可以拿到前面的配置设置到HttpSecurity的sharedObjects属性中的共享对象。前面我们说到在WebSecurityConfigurerAdapter的init(WebSecurity)方法中会先创建1个HttpSecurity,然后会给HttpSecurity添加默认的配置器
,这些添加顺序就相当于是security框架默认给我们设定好的,这个顺序会有一定的意义在里面,但是,它后面又把HttpSecurity暴露出来供我们修改,那就意味着我们可以禁用某些配置器,或者改变它原来的顺序,甚至全部移除默认的,还是再强调一遍security框架在WebSecurityConfigurerAdapter的init(WebSecurity)方法中,为HttpSecurity添加了默认的配置器,添加的这些配置器的顺序是对过滤器的配置有比较大的影响的,同时也允许我们在security为HttpSecurity添加完默认的配置器后对HttpSecurity进行修改。这是对HttpSecurity的配置器的顺序的说明
。
当HttpSecurity开启一系列流程后,与WebSecurity开启一系列流程有着很多相似点,在HttpSecurity的init()方法中,就会回调所有配置器的init(HttpSecurity)方法
,在这个init(HttpSecurity)方法中主要做的事,比如配置一些全局属性到HttpSecurity的sharedObjects中,获取HttpSecurity中的已有配置器判断是否需要初始化一些当前配置器的属性或者直接拿其它配置器中已经初始化的属性设置到当前配置器中,以方便后面构建Filter时,使用同一对象。配置器的init(HttpSecurity)方法具体做的事情可以参考不同的配置器具体的实现,可以模仿它们来玩。当HttpSecurity的init()方法完成后,会来到HttpSecurity的beforeConfigure()方法
,在这个方法中,就是在开始HttpSecurity的configure()方法方法之前,从HttpSecurity的sharedObjects属性中获取AuthenticationManagerBuilder类型的对象,即认证管理器构建器,拿到这个对象之后,调用这个对象的build方法,就会得到1个AuthenticationManager认证管理器对象,把这个认证管理器设置到HttpSecurity的sharedObjects中。那么在这里应该要有这样的疑问,这个AuthenticationManagerBuilder是什么时候放到HttpSecurity的sharedObjects中去的,能确定这个对象获取出来不是空嘛?这在WebSecurityConfigurerAdapter的getHttp()方法中,在创建HttpSecurity的时候的构建方法中,就已经断言了这个AuthenticationManagerBuilder不能是空,因此,它不可能为空,后面再看这部分具体的代码。还有一点:我们一看到这个调用AuthenticationManagerBuilder就也会开启认证管理器构建器的beforeInit -> init -> beforeConfigure -> configure-> performBuild
一系列流程,因为AuthenticationManagerBuilder也继承自AbstractConfiguredSecurityBuilder,因此,它一旦调用build()方法就开启这一系列流程,也就不奇怪了,只不过默认情况下,没人往这个AuthenticationManagerBuilder中添加它的配置器而已,所以这也是1个扩展点,我们可以拿到AuthenticationManagerBuilder后,直接给它应用自定义的配置器,它在build的时候,也会如同WebSecurity、HttpSecurity一样开启同样的流程。当走完HttpSecurity的beforeConfigure()方法,就会按照流程走HttpSecurity的configure方法
,它会将之前添加的所有配置器,回调它们的configure(HttpSecurity)方法,此时注意,我们在WebSecurityConfigurerAdapter的configure(HttpSecurity)方法中对HttpSecurity添加的配置就最终在这里生效了,虽然前面在HttpSecurity的init()方法中也生效了,但这里也再强调下,大部分的配置器会在这个回调时机往HttpSecurity的filters用来保存过滤器的属性中添加每个配置器自己的过滤器,当然,也可以同时添加多个。此时,这里还会有这样的疑问:那每个配置器添加自定的过滤器的话,配置器的添加的顺序影响到配置器的执行的顺序,那么就会影响到过滤器添加到HttpSecurity的filters中的顺序,在security中,过滤器的顺序很重要,比如:识别用户是已否登录的过滤器应该要排在靠前的位置,处理用户认证的过滤器肯定要排在识别用户是否已登录的过滤器后面,校验用户权限的过滤器应该要排在最后面。而现在我们发现我们可以自由的控制配置器的添加顺序,这样security就很难管控过滤器的顺序了,那么security如何解决这一问题的?继续跟着流程来到HttpSecurity的performBuild()方法阶段
,在这里面就会拿到HttpSecurity中的filters属性,它里面就是所有配置器最终添加的过滤器,HttpSecurity在这里会给这些过滤器排序,排序的规则就在FilterComparator中定义,也就是说Security早已为我们定义了过滤器的排列顺序
,这样HttpSecurity这个过滤器链就构建好了。
HttpSecurity中AuthenticationManagerBuilder的构建过程
通过 以上对WebSecurity和HttpSecurity的构建过程的了解,我们知道在WebSecurityConfigurerAdappter的getHttp()方法
中会创建HttpSecurity,并会给HttpSecurity配置一些默认的配置器,以往过滤器链中添加过滤器。其实,在WebSecurityConfigurerAdappter.getHttp()方法中还有1个比较重要的一点就是关于AuthenticationManagerBuilder的构建
,下面主要围绕这点分析。
WebSecurityConfigurerAdapter类中有定义如下与AuthenticationManager有关的属性:
类型 | 属性名 |
---|---|
AuthenticationManagerBuilder | authenticationBuilder |
AuthenticationManagerBuilder | localConfigureAuthenticationBldr |
boolean | disableLocalConfigureAuthenticationBldr |
boolean | authenticationManagerInitialized |
AuthenticationConfiguration | authenticationConfiguration |
AuthenticationManager | authenticationManager |
在WebSecurityConfigurerAdapter的setApplicationContext方法上,使用了@Autowired注解,因此该setApplicationContext方法会执行,在这个方法里面会为:authenticationBuilder属性赋值为创建的DefaultPasswordEncoderAuthenticationManagerBuilder
,localConfigureAuthenticationBldr属性赋值为另创建的DefaultPasswordEncoderAuthenticationManagerBuilder
所以这2个属性就被赋值了。
在WebSecurityConfigurerAdapter的setAuthenticationConfiguration(AuthenticationConfiguration)方法上,使用了@Autowired注解,自动注入了AuthenticationConfiguration这个bean,并赋值给了WebSecurityConfigurerAdapter的authenticationConfiguration属性
,那么容器中为什么会存在AuthenticationConfiguration这个bean呢?在@EnableWebSecurity注解上,引入了@EnableGlobalAuthentication注解,而@EnableGlobalAuthentication注解又使用@Import注解引入了AuthenticationConfiguration类,因此,这个AuthenticationConfiguration的Bean在容器中是没有问题的。
再回到WebSecurityConfigurerAdapter的getHttp()方法,在此方法中会先调用WebSecurityConfigurerAdapter的authenticationManager(),来得到1个AuthenticationManager认证管理器对象
。首先判断authenticationManagerInitialized是否为true,由于authenticationManagerInitialized是基本属性,刚开始默认为false,意思就是如果认证管理器还未初始化,则调用WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法,特别注意,此时传入的AuthenticationManagerBuilder方法参数为localConfigureAuthenticationBldr
。此时,如果WebSecurityConfigurerAdapter未重写configure(AuthenticationManagerBuilder)方法
,那么WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法的默认实现就是将disableLocalConfigureAuthenticationBldr属性置为true,而localConfigureAuthenticationBldr则不会有任何修改,因为它默认的实现就是要禁用localConfigureAuthenticationBldr,如果此时,我们重写了WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法
,那么disableLocalConfigureAuthenticationBldr仍然会保持为false,意思就是不禁用localConfigureAuthenticationBldr。现在至此,其实就已经把WebSecurityConfigurerAdapter中的3个可重写的configure方法的调用时机都说到了
。好的,刚刚就是在讨论这个重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法,接着来看重写与不重写影响的这个disableLocalConfigureAuthenticationBldr属性所起的作用。如果disableLocalConfigureAuthenticationBldr为true,即我们没有重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法,则调用AuthenticationConfiguration的getAuthenticationManager()来得到1个AuthenticationManager
;如果disableLocalConfigureAuthenticationBldr为false,即我们重写了WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法,则会使用localConfigureAuthenticationBldr属性的build()方法来构建AuthenticationManager()
,因为假设我们重写了这个方法,则我们可以拿到localConfigureAuthenticationBldr属性,就可以自定义配置修改这个localConfigureAuthenticationBldr,那么由我们自定义修改后的localConfigureAuthenticationBldr创建出来的AuthenticationManager认证管理器就具有较强的定制化了。大部分情况下,我们可能不会重写这个方法,如果不重写这个方法,则会走AuthenticationConfiguration的getAuthenticationManager()方法来获取认证管理器,它会默认从spring容器中寻找AuthenticationProvider类型的bean实现、UserDetailsService 实例的bean实现、PasswordEncoder实例的bean实现,添加到AuthenticationManagerBuilder认证管理器构建器中
,这样就让security利用spring容器的便捷性自动完成了security的认证管理器的配置,具体是如何创建的,在上面已经阐述过了。这样不管是采用localConfigureAuthenticationBldr的build(),还是authenticationConfiguration的getAuthenticationManager()来构建认证管理器,反正我们现在在WebSecurityConfigurerAdapter的getHttp()方法中,就已经得到了1个AuthenticationManager
。上面由AuthenticationConfiguration构建AuthenticationManager的过程,后面再具体分析
。
在WebSecurityConfigurerAdapter的getHttp()方法中,在得到上面的AuthenticationManager之后
,会将上面得到的认证管理器设置给WebSecurityConfigurerAdapter的authenticationBuilder属性的parentAuthenticationManager属性
,这说明WebSecurityConfigurerAdapter的authenticationBuilder属性才是最终用来构建认证管理器的构建器,并且上面得到的AuthenticationManager是作为parentAuthenticationManager属性的,而由WebSecurityConfigurerAdapter的authenticationBuilder属性构建出来的AuthenticationManager其实是ProviderManager,而parentAuthenticationManager就作为了ProviderManager类的parent属性了
,同时ProviderManager还会维护一个AuthenticationProvider列表
,当这个ProviderManager的所有AuthenticationProvider都不支持对Authentication进行认证时或认证得到的结果都是空时,就会交给parent属性去作认证。
在WebSecurityConfigurerAdapter的getHttp()方法中,在得到上面的AuthenticationManager之后
,并且将得到的AuthenticationManager设置到WebSecurityConfigurerAdapter的authenticationBuilder属性的parentAuthenticationManager属性
后,就开始创建sharedObjects共享属性的1个map的局部变量,里面会把localConfigureAuthenticationBldr的sharedObjects属性都塞到这个sharedObjects局部变量中,这就相当于把localConfigureAuthenticationBldr的所有共享属性,只要对localConfigureAuthenticationBldr设置的共享属性都会放入这个sharedObjects的局部变量中;然后,调用WebSecurityConfigurerAdapter的userDetailsService()方法获取userDetailsService,设置到sharedObjects局部变量中,这是1个比较重要的关键点
。这个sharedObjects局部变量后面会给到HttpSecurity的sharedObjects属性中,后续如果HttpSecurity的配置器从sharedObjects属性中拿userDetailsService,那么其实就是在这里设置进去的,讲明白了这个逻辑,再去看看WebSecurityConfigurerAdapter的userDetailsService()方法获取UserDetailsService的默认逻辑
,在WebSecurityConfigurerAdapter的userDetailsService()方法中,默认就创建了1个UserDetailsServiceDelegator作为UserDetailsService接口的实现返回,并且传入了localConfigureAuthenticationBldr、globalAuthBuilder进去,传入的这2个对象很显然就是用来创建AuthenticationManager的认证管理器的构建器,其中localConfigureAuthenticationBldr在前面已经说过了是在WebSecurityConfigurerAdapter的setApplicationContext(…)方法中回调时创建的,而globalAuthBuilder是从spring容器中查找AuthenticationManagerBuilder类型的bean得到的,那么什么地方会定义AuthenticationManagerBuilder类型的bean,在@EnableWebSecurity引入的@EnableGlobalAuthentication注解引入的AuthenticationConfiguration中就定义了AuthenticationManagerBuilder类型的bean,知道了UserDetailsServiceDelegator中传入的2个AuthenticationManagerBuilder的来源之后,就该分析UserDetailsServiceDelegator的作用了,它实现了UserDetailsService接口,内部的实现是从传入的builder列表获取第1个defaultUserDetailsService属性不为空的builder,然后把逻辑交给该builder的defaultUserDetailsService属性来实现
,在默认情况下,这2个builder的defaultUserDetailsService属性都是空,当然,我们也可以重写WebSecurityConfigurerAdapter的这个userDetailsService()方法来返回自定义提供的UserDetailsService
,这样的话,自定义提供的UserDetailsService就设置到了sharedObjects局部变量中,在创建HttpSecurity时就将此UserDetailsService设置到了HttpSecurity的sharedObjects属性中了,后续配置器在生效时机,如果从HttpSecurity的sharedObjects属性中获取UserDetailsService对应的值时,就是自定义提供的UserDetailsService了
;然后,将context作为ApplicationContext类型
,contentNegotiationStrategy作为ContentNegotiationStrategy类型,trustResolver作为AuthenticationTrustResolver类型,都放入sharedObjects局部变量中
;
在WebSecurityConfigurerAdapter的getHttp()方法中,在填充完sharedObjects局部变量后,开始调用http = new HttpSecurity(objectPostProcessor, authenticationBuilder,sharedObjects)来构建HttpSecurity了
,其中objectPostProcessor由WebSecurityConfigurerAdapter的setObjectPostProcessor(ObjectPostProcessor)方法使用了@Autowired注解自动注入了由AuthenticationConfiguration配置类引入的ObjectPostProcessorConfiguration中定义的AutowireBeanFactoryObjectPostProcessor,authenticationBuilder就是刚设置好parentAuthenticationManager属性的认证管理器构建器,sharedObjects就是刚得到的sharedObjects局部变量。在HttpSecurity的这个构造方法里,先将AuthenticationManagerBuilder类型的值设置为authenticationBuilder
,即后面的配置器生效时,从HttpSecurity的sharedObjects属性中获取AuthenticationManagerBuilder类型对应的对象时,就是该authenticationBuilder,并且可以确定该authenticationBuilder一定不会为空,并且这非常重要,因为HttpSecurity提供出来的userDetailsService(UserDetailsService)和authenticationProvider(AuthenticationProvider)方法都是基于这个HttpSecurity的sharedObjects属性的AuthenticationManagerBuilder类型对应的对象来配置的
。将sharedObjects局部变量中的数据全部放入HttpSecurity的sharedObjects属性中
,这也就是说,上面说到的sharedObjects局部变量中的数据包括:UserDetailsService,ApplicationContext、ContentNegotiationStrategy、AuthenticationTrustResolver都会放入HttpSecurity的sharedObjects属性。创建1个RequestMatcherConfigurer设置给HttpSecurity的requestMatcherConfigurer属性
,这个RequestMatcherConfigurer实际上是HttpSecurity的成员内部类,在WebSecurityConfigureAdapter的configure(HttpSecurity)方法中,可以通过httpSecurity.requestMatchers(Customizer<RequestMatcherConfigurer>)拿到此配置器,然后调用RequestMatcherConfigurer里面提供的方法,其实就是修改HttpSecurity的requestMatcher属性,来修改这个过滤器链匹配哪些请求,这可以在HttpSecurity的performBuild()方法和FilterChainProxy的getFilters(HttpServletRequest)中起作用
。
在WebSecurityConfigurerAdapter的getHttp()方法中,刚刚创建完HttpSecurity后,并且进行了一些初始化,现在要开始往HttpSecurity中添加配置器了
。如果WebSecurityConfigurerAdapter的disableDefaults不为true的话,即没有禁用默认的HttpSecurity的配置器,则会给HttpSecurity添加一些配置器,它们是:csrf、exceptionHandling、headers、sessionManagement、securityContext、requestCache、anonymous、servletApi、DefaultLoginPageConfigurer、logout
,默认情况下没有禁用,因此这些配置器都会添加上去。可以通过将WebSecurityConfigurerAdapter的disableDefaults置为true,就可以禁用这些默认的配置器。然后,调用configure(HttpSecurity)方法来继续为HttpSecurity添加配置器或对该HttpSecurity进行修改,在WebSecurityConfigurerAdapter的configure(HttpSecurity)方法中有默认的实现,会往HttpSecurity添加配置器:authorizeRequests任何请求都需要认证、formLogin、httpBasic
,我们也可以重写WebSecurityConfigurerAdapter的configure(HttpSecurity)方法在添加默认配置器的基础上继续配置该HttpSecurity
,并且也可以调用http的disable(…)方法删除前面所添加的默认配置器。明白了这个,那么配置HttpSecurity的就比较简单了,但是还是值得提醒的是,这里仍然只是在给HttpSecurity添加配置类
,并没有让这些配置类在此时生效,一定是调用HttpSecurity的build()方法为入口点,才会开始真正使用配置器配置HttpSecurity
。至此,WebSecurityConfigurerAdapter的getHttp()方法就说完了。
AuthenticationConfiguration构建AuthenticationManager的过程
,在开始分析AuthenticationConfiguration的getAuthenticationManager()方法之前,先看AuthenticationConfiguration中定义的bean,了解这些定义的bean对于构建AuthenticationManager很大的帮助
。首先,使用了@Bean修饰了AuthenticationConfiguration的authenticationManagerBuilder(ObjectPostProcessor<Object>,ApplicationContext)方法而定义了AuthenticationManagerBuilder
,其中ObjectPostProcessor是AuthenticationConfiguration类上的使用@Import注解引入的ObjectPostProcessorConfiguration引入了AutowireBeanFactoryObjectPostProcessor,继续再看,在AuthenticationConfiguration的这个authenticationManagerBuilder(..)方法中,创建了LazyPasswordEncoder
,它会在具体工作时从spring容器中寻找PasswordEncoder的bean,如果找不到PasswordEncoder的bean,则会调用PasswordEncoderFactories的createDelegatingPasswordEncoder()方法来创建1个DelegatingPasswordEncoder,它里面囊括了security的所有加密器,之后,把ObjectPostProcessor和LazyPasswordEncoder都作为DefaultPasswordEncoderAuthenticationManagerBuilder构造方法的参数来创建认证管理器的构建器对象
,这个认证管理器的构建器对象就是用来创建认证管理器的构建器,这个构建器最终构建出来的认证管理器的实现类其实就是ProviderManager,它包含1个AuthenticationProvider的列表和1个parent的AuthenticationManager
,显然,这里构建的这个ProviderManager默认是没有父parent的 ,因为,现在创建的这个ProviderManager就是作为WebSecurityConfiguration的authenticationBldr属性的父parent的,当然在DefaultPasswordEncoderAuthenticationManagerBuilder构建之前会给这个Builder添加配置器,这里的会寻找容器中的所有GlobalAuthenticationConfigurerAdapter的bean都作为该Builder的配置器。说到这个GlobalAuthenticationConfigurerAdapter的配置器,然后,我们再看AuthenticationConfiguration的3个定义GlobalAuthenticationConfigurerAdapter配置器的方法
,这3个GlobalAuthenticationConfigurerAdapter的bean后面都会添加给上面刚刚说到的DefaultPasswordEncoderAuthenticationManagerBuilder,它们分别是:EnableGlobalAuthenticationAutowiredConfigurer,InitializeAuthenticationProviderBeanManagerConfigurer,InitializeUserDetailsBeanManagerConfigurer
,它们应用到认证管理器构建器的顺序分别是:100,Integer.MAX_VALUE- 5000 - 100,Integer.MAX_VALUE- 5000,再来看看它们分别的作用:EnableGlobalAuthenticationAutowiredConfigurer会在init(AuthenticationManagerBuilder)方法中调用getBeansWithAnnotation(@EnableGlobalAuthentication)
获取所有标注了的@EnableGlobalAuthentication注解的bean、InitializeAuthenticationProviderBeanManagerConfigurer会在init(AuthenticationManagerBuilder)方法中给AuthenticationManagerBuilder添加InitializeAuthenticationProviderManagerConfigurer
,值得注意的是为什么要在init方法中去再次为AuthenticationManagerBuilder添加另外1个配置器呢?在init(AuthenticationManagerBuilder)方法中添加就可以保证是添加到已有配置器的后面,作为兜底方案,假设我们自定义了GlobalAuthenticationConfigurerAdapter的bean,那么我们给AuthenticationManagerBuilder添加了自定义的配置器,那肯定先走我们自定义的配置器的逻辑,说清楚这一点后,再来看InitializeAuthenticationProviderManagerConfigurer的具体作用
:如果AuthenticationManagerBuilder已配置,什么叫已配置,就是AuthenticationManagerBuilder的authenticationProviders列表属性或者parentAuthenticationManager不为空,那么就啥也不干,如果尚未配置,即AuthenticationManagerBuilder的authenticationProviders列表属性或者parentAuthenticationManager都为空,那么就从容器中找1个AuthenticationProvider类型的bean,注意,如果尚未配置,当容器中有1个AuthenticationProvider类型的bean时,就会将此AuthenticationProvider类型的bean添加到AuthenticationManagerBuilder的authenticationProviders列表中,如果超过1个,则会将第1个AuthenticationProvider类型的bean添加到AuthenticationManagerBuilder的authenticationProviders列表中,如果容器中1个都没有,则啥也不干
,再来看InitializeUserDetailsManagerConfigurer的具体作用
:如果AuthenticationManagerBuilder已配置,那么就啥也不干,其中已配置的说明同上,如果尚未配置,则从容器中找1个UserDetailsService类型的bean,当容器中只有1个UserDetailsService类型的bean时,会使用找到的这个UserDetailsService类型的bean,如果超过1个,则会获取第1个UserDetailService类型的bean,如果容器中1个都没有,则啥也不干
,如果获取到了UserDetailsService类型的bean,就再调用DaoAuthenticationProvider无参构造方法创建1个DaoAuthenticationProvider对象,DaoAuthenticationProvider的无参方法中会调用PasswordEncoderFactories的createDelegatingPasswordEncoder()方法来创建1个DelegatingPasswordEncoder,它里面囊括了security的所有加密器,同样的方法,去容器中获取PasswordEncoder类型的bean,如果容器中有1个则就获取这个,如果有超过1个则获取第1个,如果没有,则就用DaoAuthenticationProvider对象构造方法中所默认创建的DelegatingPasswordEncoder,再将此DaoAuthenticationProvider添加给AuthenticationManagerBuilder,其实在大部分简单的情况下,我们使用的就是这种方式:我们在容器中定义UserDetailsService类型的bean和PasswordEncoder类型的bean,然后就会经过这里的这个InitializeUserDetailsManagerConfigurer配置器的处理
在AuthenticationConfiguration中分析完了authenticationManagerBuilder(…)和3个@Bean定义的认证管理器构建器的配置器方法后,再看AuthenticationConfiguration的getAuthenticationManager()方法
,再来强调1下这个方法在上下文中的含义:WebSecurityConfigurerAdapter当disableLocalConfigureAuthenticationBldr属性为true时,即禁用localConfigureAuthenticationBldr属性时,才会调用authenticationConfiguration.getAuthenticationManager()方法返回1个认证管理器作为WebSecurityConfigurerAdapter的authenticationBuilder的parentAuthenticationManager属性,默认情况下,我们如果不重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法时,就会走authenticationConfiguration.getAuthenticationManager()方法返回认证管理器的逻辑。如果我们重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法时,就可以拿到WebSecurityConfigurerAdapter的localAuthenticationManagerBldr属性了,并且构建作为authenticationManager将由WebSecurityConfigurerAdapter的localAuthenticationManagerBldr来完成而非WebSecurityConfigurerAdapter的AuthenticationConfiguration.getAuthenticationManager()来完成
。清楚了这一点过后,再来分析AuthenticationConfiguration的getAuthenticationManager()方法:先从容器中获取AuthenticationManagerBuilder类型的bean
,那么这个AuthenticationManagerBuilder类型的bean会在哪里定义呢?就是在AuthenticationConfiguration本类中,就上面刚刚提到。获取到这个AuthenticationManagerBuilder类型的bean之后,就会把从spring容器中得到的GlobalAuthenticationConfigurerAdapter列表都添加到这个AuthenticationManagerBuilder类型的bean
中,那么是什么时候获取到GlobalAuthenticationConfigurerAdapter列表的呢?就在AuthenticationConfiguration本类中定义了3个@Bean声明的GlobalAuthenticationConfigurerAdapter类型的bean,并在AuthenticationConfiguration本类的setGlobalAuthenticationConfigurers(List<GlobalAuthenticationConfigurerAdapter>),调用刚从容器中获取AuthenticationManagerBuilder类型的bean的build()方法,开启AuthenticationManagerBuilder的一系列构建流程,构建过程与WebSecurity和HttpSecurity类似,并且配置器就是那3个@Bean声明的GlobalAuthenticationConfigurerAdapter类型的bean,也允许自定义GlobalAuthenticationConfigurerAdapter的bean
,至此,AuthenticationManagerBuilder就构建完成了。
在很多项目里或案例里,可能会经常看到这样的代码:在自定义WebSecurityConfigurerAdapter的子类中使用@Bean定义重写了 authenticationManagerBean()方法,方法内部仅仅return super.authenticationManagerBean(),那么这是什么意思呢?
WebSecurityConfigurerAdapter类的authenticationManagerBean()方法创建了AuthenticationManagerDelegator,并且传入了WebSecurityConfigurerAdapter的authenticationBuilder属性和context属性
,我们来到AuthenticationManagerDelegator,发现它的实现就是获取WebSecurityConfigurerAdapter的authenticationBuilder属性所最终构建的结果作为委托对象,全部交给此委托对象来完成认证流程,所以对WebSecurityConfigurerAdapter的authenticationManagerBean()方法使用@Bean的方式定义在容器中,其实就是将WebSecurityConfigurerAdapter的authenticationBuilder属性构建的对象暴露出来给外界使用
,同时,这个构建出来的对象也正在被HttpSecurity的sharedObejcts类型的AuthenticationManager类型所引用,也就是说,其它从HttpSecurity的sharedObejcts属性中获取AuthenticationManager类型的对象与这里调用WebSecurityConfigurerAdapter的authenticationManagerBean()暴露出来的对象其实是同一个。
WebSecurityConfigurerAdapter的userDetailsService()方法分析
:WebSecurityConfigurerAdapter的这个userDetailsService()方法,只在WebSecurityConfigurerAdapter的createSharedObjects()方法里先调用WebSecurityConfigurerAdapter的userDetailsService()方法获取UserDetailsService对象,然后将这个获取的UserDetailsService对象,设置为sharedObjects局部变量中UserDetailsService类型对应的值,而sharedObjects局部变量中的所有对象都会放入到HttpSecurity的sharedObjects属性中,后面配置器从HttpSecurity的sharedObjects属性中获取UserDetailsService类型的话,获取的其实就是这里获取的UserDetailsService对象了
。明白了这一点之后,再具体看WebSecurityConfigurerAdapter的userDetailsService()方法,它会从容器中查找AuthenticationManagerBuilder类型的bean作为globalAuthBuilder参数,很显然,这个AuthenticationManagerBuilder类型的bean我们在AuthenticationConfiguration中看到过定义了这个类型的bean,然后创建1个UserDetailsServiceDelegator对象,并将localConfigureAuthenticationBldr,globalAuthBuilder作为参数列表传入
,我们来到UserDetailsServiceDelegator发现它的实现就是遍历构造参数中传入的AuthenticationManagerBuilder列表,获取它们的defaultUserDetailsService属性,按顺序获取,将第1个获取的不为空的defaultUserDetailsService属性作为最终的委托对象去根据用户名获取UserDetails
,我们知道,当我们不重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法时
,会使用AuthenticationConfiguration的getAuthenticationManager()方法来获取authenticationManager作为WebSecurityConfigurerAdapter的authenticationBuilder属性对象的parentAuthenticationManager属性,在这种情况下WebSecurityConfigurerAdapter的localConfigureAuthenticationBldr属性的defaultUserDetailsService属性就是空,而此时在WebSecurityConfigurerAdapter的userDetailsService()方法中,从容器中获取的AuthenticationManagerBuilder就是AuthenticationConfiguration中使用@Bean修饰的authenticationManagerBuilder(…)方法定义的AuthenticationManagerBuilder,它的defaultUserDetailsService属性仍然默认是空。当我们重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法时
,就可以拿到并修改WebSecurityConfigurerAdapter的localConfigureAuthenticationBldr属性,此时,这里会使用localConfigureAuthenticationBldr的build()方法来获取authenticationManager作为WebSecurityConfigurerAdapter的authenticationBuilder属性对象的parentAuthenticationManager属性,在这种情况下WebSecurityConfigurerAdapter的localConfigureAuthenticationBldr属性的defaultUserDetailsService属性就可以由我们自己设置,但是如果我们不设置的话,它仍然会默认为空,但是,此时我们拿到了localConfigureAuthenticationBldr的话,就可以调用AuthenticationManagerBuilder的userDetailsService(userDetailsService)方法来设置AuthenticationManagerBuilder的defaultUserDetailsService属性了。不管我们是否重写或者不重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法,WebSecurityConfigurerAdapter的userDetailsService()方法里传入的localConfigureAuthenticationBldr,globalAuthBuilder参数列表,它们的defaultUserDetailsService属性都会默认为空,而如果默认为空,则创建的UserDetailsServiceDelegator将无法正常工作,会抛出异常,并且我们知道这里创建的这个UserDetailsServiceDelegator将会添加到sharedObjects局部变量中,随后添加到HttpSecurity的sharedObjects属性中UserDetailsService类型中去,因此,我们也可以考虑在这2种情况下都可以想到办法设置上它们的defaultUserDetailsSesrvice属性,从而让HttpSecurity的sharedObjects属性对应的UserDetailsService类型对应的UserDetailsServiceDelegator将查询UserDetails的实现交给对应的defaultUserDetailsSesrvice属性来完成,后续配置器从HttpSecurity的sharedObjects属性中获取UserDetailsService类型的UserDetailsServiceDelegator也就能委托给对应的defaultUserDetailsSesrvice属性来完成了
,或者直接点:也直接重写WebSecurityConfigurerAdapter的userDetailsService()来直接给HttpSecurity的sharedObjects属性对应的UserDetailsService类型赋值就好了。其实到到现在就明白了:WebSecurityConfigurerAdapter的userDetailsService()方法就是给HttpSecurity的sharedObjects属性对应的UserDetailsService类型赋值用的
WebSecurityConfigurerAdapter的userDetailsServiceBean()方法分析
:WebSecurityConfigurerAdapter的userDetailsServiceBean()方法与WebSecurityConfigurerAdapter的userDetailsService()方法中的代码实现完全一样,所以实现上就直接看WebSecurityConfigurerAdapter的userDetailsService()方法的分析的前面一部分即可。它会从容器中查找AuthenticationManagerBuilder类型的bean作为globalAuthBuilder参数,很显然,这个AuthenticationManagerBuilder类型的bean我们在AuthenticationConfiguration中看到过定义了这个类型的bean,然后创建1个UserDetailsServiceDelegator对象,并将localConfigureAuthenticationBldr,globalAuthBuilder作为参数列表传入
,可见实现上与WebSecurityConfigurerAdapter的userDetailsService()方法中的代码实现完全一致,那么WebSecurityConfigurerAdapter的userDetailsServiceBean()方法存在的价值是什么呢?其实就是把传入给UserDetailsServiceDelegator对象的这2个AuthenticationManagerBuilder,按顺序看哪个的的defaultUserDetailsService属性不为空,封装为UserDetailsServiceDelegator,暴露成1个bean,提供给外界
。
UserDetailsServiceAutoConfiguration自动配置UserDetailsService
的分析:当我们只是想尝试下spring security中的功能,跑个demo的时候,这个就能派上用场。UserDetailsServiceAutoConfiguration是自动配置类,会被springboot默认加载到。当容器中存在AuthenticationManager,或者AuthenticationProvider,或者UserDetailsService时,这个自动配置类就会被禁用掉
,这里可以想一下,为什么security这样去设计,至于被禁用的原因是因为@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class }),但是,这并不是这样去设计的原因,而是达到这样效果的实现方式,原因是:在AuthenticationConfiguraiton里定义了1个AuthenticationManagerBuilder的bean,然后又定义了3个GlobalAuthenticationConfigurerAdapter去配置这个AuthenticationManagerBuilder的bean,这3个GlobalAuthenticationConfigurerAdapter中有2个就是会去容器中先查找AuthenticationProvider的bean添加到这个AuthenticationManagerBuilder的bean,当没有找到AuthenticationProvider的bean时,又会去容器中查找UserDetailsService的bean,当还是没有找到UserDetailsService的bean时,又会去容器中查找AuthenticationManager的bean,所以按照这样的找法,只要我们定义了这3种类型中的任何1个bean,它默认都会配置到这个AuthenticationManagerBuilder的bean中,而一旦配置到了AuthenticationManagerBuilder的bean中去了,意味着就给了security认证用户的逻辑,既然给了逻辑给security,security就不用自己生成用户了,如果没给逻辑给security,那么security自动生成1个用户作为认证用户的逻辑,这也是可以理解的
。当然,这是建立在WebSecurityConfigurerAdapter默认禁用localAuthenticationManagerBldr的前提下,而使用AuthenticationConfiguration的getAuthenticationManager()方法来得到认证管理器时。当这个UserDetailsServiceAutoConfiguration自动配置类生效时,即容器中不存在上述的3个bean时,就会由UserDetailsServiceAutoConfiguration自动配置类引入InMemoryUserDetailsManager这个bean,它实现了UserDetailsService接口,并且使用内存的方式维护了1个users属性来保存用户
,并且会默认创建1个名为user的用户,密码默认为1个随机生成的uuid打印在控制台,也可以通过spring.security.user.name来指定用户名,spring.security.user.password和spring.security.user.passwordGenerated=false来指定密码,spring.security.user.roles来指定角色
,而引入了InMemoryUserDetailsManager这个bean后,仍然会用在AuthenticationConfiguration的在调用getAuthenticationManager()方法中应用InitializeUserDetailsBeanManagerConfigurer这个配置器时,找到UserDetailsServiceAutoConfiguration引入的InMemoryUserDetailsManager这个bean,而配置到AuthenticationManagerBuilder中。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)