Spring Security(2)——探索篇(源码分析)

原创性声明:本文完全为笔者原创,请尊重笔者劳动力。转载务必注明原文地址。

在上一篇Spring Security(1)——基础篇(引入)中,我在一个空的Spring Boot项目中引入了Spring Security,并且做了基本的配置。成功的应用了Spring Security,而在项目里,我手写的类中只涉及三个Spring Security的类,他们分别是:

1.WebSecurityConfigurerAdapter
2.HttpSecurity
3.AuthenticationManagerBuilder

先从AuthenticationManagerBuilder入手,该类的描述是这样的:

AuthenticationManagerBuilder.java

译文大概是:SecurityBuilder用于创建一个AuthenticationManager。 允许轻松构建内存验证,LDAP身份验证,基于JDBC的身份验证,添加UserDetailsService以及添加AuthenticationProvider。

显然,我们采用的是内存验证

ctrl+q(用的idea)再查看inMemoryAuthentication()方法的签名:

AuthenticationManagerBuilder.inMemoryAuthentication()签名

译文:将内存验证添加到AuthenticationManagerBuilder并返回一个InMemoryUserDetailsManagerConfigurer以允许定制内存验证。 此方法还可确保UserDetailsService可用于getDefaultUserDetailsService()方法。 请注意,其他UserDetailsService可能会将此UserDetailsService替换为默认值。

进入inMemoryAuthentication()方法

AuthenticationManagerBuilder.inMemoryAuthentication()方法体

查看apply的签名描述:Captures the UserDetailsService from any UserDetailsAwareConfigurer,意思是从任何UserDetailsAwareConfigurer捕获UserDetailsService。,查看方法体:

AuthenticationManagerBuilder.apply()

它调用了inMemoryAuthentication()方法体中通过new创建的InMemoryUserDetailManagerConfigurer的实例的getUserDetailsService()方法,并将其返回值给了当前类型为UserDetailsService的属性defaultUserDetailsService,另外还调用了超类的apply()方法,查看其签名,描述为将SecurityConfigurerAdapter应用于此SecurityBuilder并调用SecurityConfigurerAdapter.setBuilder(SecurityBuilder),不理解先不必纠结。进入超类的apply()就可以发现,它只是给configurer添加了两个属性,便将其返回了:

AbstractConfiguredSecurityBuilder.apply()

因此,整个InMemoryUserDetailsManagerConfigurer方法返回的值就是其中通过new创建的InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>实例,同样我们进入这个构造函数,就不难发现,它在构造函数中调用了父级的构造函数,父级构造函数中又调用了祖父级构造函数。层层往上,继承结构为:

AbstractDaoAuthenticationConfigurer
  - UserDetailsServiceConfigurer
    - UserDetailsManagerConfigurer
      - InMemoryUserDetailsManagerConfigurer

而在InMemoryUserDetailsManagerConfigurer构造函数中,给父构造函数传递了一个通过new创建的InMemoryUserDetailsManager实例。而在AbstractDaoAuthenticationConfigurer构造函数中,终于做了些实事:

AbstractDaoAuthenticationConfigurer构造函数

AbstractDaoAuthenticationConfigurer的属性userDetailsServiceprovider的属性userDetailsService赋值。

再回到SecurityConfig.javawithUser()方法,进入方法体:

withUser()

不难发现,里面创建了一个UserDetailsBuilder实例,并将其添加到List<UserDetailsBuilder> userBuilders中,同时返回这个创建的UserDetailsBuilder(显然是为了调用password()设置密码), 此时在SecurityConfig中再调用and()可以返回对应的Builder,便可以再次调用withUser()添加用户。

概括来,configureGlobal()方法中主要做了以下几件事:
1.为AbstractDaoAuthenticationConfigurer实例初始化了userDetailsService属性,以及为属性DaoAuthenticationProvider provider设置了userDetailsService属性值。
2.创建了UserBuilder对象,并设置了用户名和密码。

看到这里我是很迷糊的,想说:“那又怎样?”,不谈别的,光类名和方法名的命名就很晕了,跟路痴一样(虽然我方向感极好),傻傻分不清。既然没有头绪,就去看一下官网文档。看到的第一个类(接口)就是:AuthenticationManager:

AuthenticationManager

查看该接口的描述Processes an Authentication request.(处理身份验证请求),文档也写到:The main strategy interface for authentication is AuthenticationManager which only has one method(认证的主要策略接口是AuthenticationManager,只有一种方法authenticate),再查看authenticate方法描述:
AuthenticationManager.authenticate()

注意查看该方法的具体描述。

可以看到这个接口也没有再继承别的接口了,可以确定就是这个接口和这个方法了。Spring Security就是用这个接口定义作为全局认证身份管理器,部分网上教程说这是唯一的全局认证身份管理器,是否唯一在官网上没有看到,总之Spring Security就是用AuthenticationManager接口来处理认证请求的。那这跟之前分析得出的两点结论貌似还没接上关系。继续官方文档的那篇文章,很快,第二个接口就出来了ProviderManager,(provider这个单词跟之前分析的AbstractDaoAuthenticationConfigurer类中的provider属性是不是有点关系呢?)。

在idea中点击AuthenticationManager旁边的小图标。可以热链接到众儿子们:

进入子类ProviderManager

进入这个ProviderManager子类。结合文档内容:

ProviderManager官方简述

AuthenticationManager的认证功能由最常用的实现者之一ProviderManager来实现,而ProviderManager没有做实事,而是将认证的实现委托给了一个List<AuthenticationProvider> providers

按照官方描述:

ProviderManager官方描述

ProviderManager可以通过委托给一个AuthenticationProviders链来支持同一应用程序中的多个不同的身份验证机制,而这个AuthenticationProviders链就是ProviderManager类中维护的List<AuthenticationProvider>。而这个不同的身份验证机制指的就是多种验证方式:用户名密码凭证登录手机登录指纹登录、甚至是IphoneX饱受吐槽的FaceID认证,每一种认证方式对应一个AuthenticationProviders

再进入AuthenticationProvider,发现也是一个顶层接口,和AuthenticationManager非常像,多了一个support方法,查看签名可以知道它的功能是判断是否支持某种验证方式。

再看另一个主要的方法authenticate():

AuthenticationProvider.authenticate()签名

译文:使用与AuthenticationManager.authenticate(Authentication)相同的合同执行身份验证。 **参数**:认证 - 认证请求对象。 **返回**:一个完全认证的对象,包括凭据。 如果AuthenticationProvider无法支持对所传递的Authentication对象进行身份验证,则返回null。 在这种情况下,将会尝试支持所提供的Authentication类的下一个AuthenticationProvider。**抛出**:AuthenticationException - 如果身份验证失败。
AuthenciationManager内定义的authentication()方法基本一致。还可以看出,如果传入的待认证凭据Authentication认证失败,会采用ProviderManagerList<AuthenticationProvider>的下一个AuthenticationProvider进行认证。

由于AuthenticationProvider是一个顶层接口,用同样的方法查看他的实现类:

AuthenticationProvider的众多实现类

你应该注意到DaoAuthenticationProvider了,我们之前在AbstractDaoAuthenticationConfigurer类的构造函数中操作的属性provider就是这个类型:

AbstractDaoAuthenticationConfigurer中的属性provider

进入DaoAuthenticationProvider,发现它虽然没有实现AuthenticationProviderauthentication方法,但它继承了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是如何调用AuthenticationManagerauthentication()方法的(过滤器?拦截器?)
3.Spring Security的这些接口和类庞杂的继承实现网中,接口和类的命名似乎有规律可言。
4.如果不是基于内存的单用户(而是采取数据库),又是如何处理的?至少得操作db,查user,进行校验比对了,那又该如何设置。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,083评论 19 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,989评论 6 342
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 11,242评论 6 13
  • 目录 -6- 吉安娜在导师安东尼达斯门口站了一夜。 整个晚上她都忐忑不安,关于阿尔萨斯的消息,她一定要立刻知道。 ...
    小羡鱼阅读 1,148评论 13 9
  • 感恩天气变冷,虽然我不喜欢冬天,但是还是希望冬天的到来,因为这是季节的交替。 感恩环卫工人的付出,这么冷的天还在不...
    孔美荣阅读 191评论 0 0