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,进行校验比对了,那又该如何设置。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

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