原创性声明:本文完全为笔者原创,请尊重笔者劳动力。转载务必注明原文地址。
在上一篇Spring Security(1)——基础篇(引入)中,我在一个空的Spring Boot
项目中引入了Spring Security
,并且做了基本的配置。成功的应用了Spring Security
,而在项目里,我手写的类中只涉及三个Spring Security
的类,他们分别是:
1.
WebSecurityConfigurerAdapter
2.HttpSecurity
3.AuthenticationManagerBuilder
先从AuthenticationManagerBuilder
入手,该类的描述是这样的:
译文大概是:
SecurityBuilder用于创建一个AuthenticationManager。 允许轻松构建内存验证,LDAP身份验证,基于JDBC的身份验证,添加UserDetailsService以及添加AuthenticationProvider。
显然,我们采用的是内存验证
ctrl+q
(用的idea
)再查看inMemoryAuthentication()
方法的签名:
译文:
将内存验证添加到AuthenticationManagerBuilder并返回一个InMemoryUserDetailsManagerConfigurer以允许定制内存验证。 此方法还可确保UserDetailsService可用于getDefaultUserDetailsService()方法。 请注意,其他UserDetailsService可能会将此UserDetailsService替换为默认值。
进入inMemoryAuthentication()
方法
查看apply
的签名描述:Captures the UserDetailsService from any UserDetailsAwareConfigurer
,意思是从任何UserDetailsAwareConfigurer捕获UserDetailsService。
,查看方法体:
它调用了inMemoryAuthentication()
方法体中通过new
创建的InMemoryUserDetailManagerConfigurer
的实例的getUserDetailsService()
方法,并将其返回值给了当前类型为UserDetailsService
的属性defaultUserDetailsService
,另外还调用了超类的apply()
方法,查看其签名,描述为将SecurityConfigurerAdapter应用于此SecurityBuilder并调用SecurityConfigurerAdapter.setBuilder(SecurityBuilder)
,不理解先不必纠结。进入超类的apply()
就可以发现,它只是给configurer
添加了两个属性,便将其返回了:
因此,整个InMemoryUserDetailsManagerConfigurer
方法返回的值就是其中通过new
创建的InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
实例,同样我们进入这个构造函数,就不难发现,它在构造函数中调用了父级的构造函数,父级构造函数中又调用了祖父级构造函数。层层往上,继承结构为:
AbstractDaoAuthenticationConfigurer
- UserDetailsServiceConfigurer
- UserDetailsManagerConfigurer
- InMemoryUserDetailsManagerConfigurer
而在InMemoryUserDetailsManagerConfigurer
构造函数中,给父构造函数传递了一个通过new
创建的InMemoryUserDetailsManager
实例。而在AbstractDaoAuthenticationConfigurer
构造函数中,终于做了些实事:
为AbstractDaoAuthenticationConfigurer
的属性userDetailsService
和provider
的属性userDetailsService
赋值。
再回到SecurityConfig.java
的withUser()
方法,进入方法体:
不难发现,里面创建了一个UserDetailsBuilder
实例,并将其添加到List<UserDetailsBuilder> userBuilders
中,同时返回这个创建的UserDetailsBuilder
(显然是为了调用password()设置密码), 此时在SecurityConfig
中再调用and()
可以返回对应的Builder
,便可以再次调用withUser()
添加用户。
概括来,configureGlobal()
方法中主要做了以下几件事:
1.为AbstractDaoAuthenticationConfigurer
实例初始化了userDetailsService
属性,以及为属性DaoAuthenticationProvider provider
设置了userDetailsService
属性值。
2.创建了UserBuilder
对象,并设置了用户名和密码。
看到这里我是很迷糊的,想说:“那又怎样?”,不谈别的,光类名和方法名的命名就很晕了,跟路痴一样(虽然我方向感极好),傻傻分不清。既然没有头绪,就去看一下官网文档??吹降牡谝桓隼啵ń涌冢┚褪牵?code>AuthenticationManager:
查看该接口的描述
Processes an Authentication request.
(处理身份验证请求),文档也写到:The main strategy interface for authentication is AuthenticationManager which only has one method(认证的主要策略接口是AuthenticationManager,只有一种方法authenticate)
,再查看authenticate
方法描述:注意查看该方法的具体描述。
可以看到这个接口也没有再继承别的接口了,可以确定就是这个接口和这个方法了。Spring Security
就是用这个接口定义作为全局认证身份管理器
,部分网上教程说这是唯一的全局认证身份管理器
,是否唯一在官网上没有看到,总之Spring Security
就是用AuthenticationManager
接口来处理认证请求的。那这跟之前分析得出的两点结论貌似还没接上关系。继续官方文档的那篇文章,很快,第二个接口就出来了ProviderManager
,(provider
这个单词跟之前分析的AbstractDaoAuthenticationConfigurer
类中的provider
属性是不是有点关系呢?)。
在idea中点击AuthenticationManager
旁边的小图标??梢匀攘唇拥街诙用牵?br>
进入这个ProviderManager
子类。结合文档内容:
AuthenticationManager
的认证功能由最常用的实现者之一ProviderManager
来实现,而ProviderManager
没有做实事,而是将认证的实现委托给了一个List<AuthenticationProvider> providers
。
按照官方描述:
ProviderManager
可以通过委托给一个AuthenticationProviders
链来支持同一应用程序中的多个不同的身份验证机制,而这个AuthenticationProviders
链就是ProviderManager
类中维护的List<AuthenticationProvider>
。而这个不同的身份验证机制指的就是多种验证方式:用户名密码凭证登录
、手机登录
、指纹登录
、甚至是IphoneX饱受吐槽的FaceID
认证,每一种认证方式对应一个AuthenticationProviders
。
再进入AuthenticationProvider
,发现也是一个顶层接口,和AuthenticationManager
非常像,多了一个support
方法,查看签名可以知道它的功能是判断是否支持某种验证方式。
再看另一个主要的方法authenticate()
:
译文:
使用与AuthenticationManager.authenticate(Authentication)相同的合同执行身份验证。 **参数**:认证 - 认证请求对象。 **返回**:一个完全认证的对象,包括凭据。 如果AuthenticationProvider无法支持对所传递的Authentication对象进行身份验证,则返回null。 在这种情况下,将会尝试支持所提供的Authentication类的下一个AuthenticationProvider。**抛出**:AuthenticationException - 如果身份验证失败。
和
AuthenciationManager
内定义的authentication()
方法基本一致?;箍梢钥闯觯绻氲拇现て揪?code>Authentication认证失败,会采用ProviderManager
中List<AuthenticationProvider>
的下一个AuthenticationProvider
进行认证。
由于AuthenticationProvider
是一个顶层接口,用同样的方法查看他的实现类:
你应该注意到DaoAuthenticationProvider
了,我们之前在AbstractDaoAuthenticationConfigurer
类的构造函数中操作的属性provider
就是这个类型:
进入DaoAuthenticationProvider
,发现它虽然没有实现AuthenticationProvider
中authentication
方法,但它继承了AbstractUserDetailsAuthenticationProvider
类,而这个类也继承自AuthenticationProvider
,并且它实现了authentication
方法。
DaoAuthenticationProvider
中只是没有去重写而已,看到这儿虽然很多具体的实现、机制还是并不了解。但大概有就那么点意思了:
项目启动时,通过注入的方式,实例化了
AuthenticationManagerBuilder
对象,执行了configureGlobal
方法,在其中执行了inMemoryAuthentication()
,它初始化了一个userDetailsService
,接着接连调用withUser()
和password()
,定义了一个用户,这个用户的信息被放到了userDetailsService
能访问的地方,同时这个userDetailsService
被设置给了DaoAuthenticationProvider provider
的一个属性userDetailsService
。
另一方面。在登录请求时,AuthenticationProvider
会被真正用来处理认证,而它的实现类负责了认证的代码实现,而它的实现类中就有DaoAuthenticationProvider
,因此就可以想到它在具体认证的时候,就可以访问到userDetailsService
,而里面就有我们用.withUser("user").password("password").roles("USER")
在内存中创建的这个用户了。
大概是这么个意思,里面也许会有些不对或不准确的地方。但整体的分析下来,对理解Spring Security
的认证机制还是有所帮助的。
此外,这一通分析也打开了一些新的窗口。比如:
1.出现了一个貌似很重要的类UserDetailsService,后面可以进行探索。
2.Spring Security
是如何调用AuthenticationManager
的authentication()
方法的(过滤器?拦截器?)
3.Spring Security
的这些接口和类庞杂的继承实现网中,接口和类的命名似乎有规律可言。
4.如果不是基于内存的单用户(而是采取数据库),又是如何处理的?至少得操作db,查user,进行校验比对了,那又该如何设置。