SpringSecurity从关门放狗到关门打狗(三)

SpringSecurity从关门放狗到关门打狗(三)

———————SpringSecurity登录认证授权原理

上一期我们写了个基本的授权认证功能,结尾的时候说这期来点骚的,但是这一期恐怕让各位失望了,这期咱们来搞点基础的东西–SpringSecurity登录认证授权原理。虽然这个其他人很多博客里也都写过,但是我还是要再说一下,因为后面我们可能会用到,而且也可以让我们对这个安全框架有更加深层次(♂)的了解,本篇内容参考(cv)了博客SpringSecurity认证流程源码详解Spring-Security登录认证授权原理,人家已经讲得很详细了,我就是整理加丰富一下,各位不要骂我哈。

开整!

一、认证处理流程说明

首先,只要是一提springsecurity认证流程,这张《认证处理流程说明原理图》是跳不过去的,下面咱们就按照这张图的流程结合源码走一遍。
认证处理流程说明原理图

先贴一下源码的仓库大家可以拉取一下对照着看,或者也可以从IDEA中点击 DownLoad Sources下载源码进行查看

https://github.com/spring-projects/spring-security

UsernamePasswordAuthenticationFilter

我们先来查找一下这个了UsernamePasswordAuthenticationFilter.java类,看名字我们就知道这是个过滤器(实际上Spring Security就是通过过滤器链实现的),该过滤器是用来处理用户认证逻辑的。在前台输入完用户名密码之后就会经过一些列的过滤器,其中就有要UsernamePasswordAuthenticationFilter类中去获取请求中的用户名和密码,去构建一个UsernamePasswordAuthenticationToken对象。
在这里插入图片描述
UsernamePasswordAuthenticationFilter.java类中的主要的方法attemptAuthentication(实际上这个方法是在本过滤器的抽象类AbstractAuthenticationProcessingFilter.javadoFilter方法中调用的)中我们可以看到:
(1)可以看到它默认的登录请求url是"/login",并且只允许POST方式的请求

(2)他是通过obtainUsername()方法默认根据参数名为"username"和"password"来获取用户名和密码的

(3)通过构造方法实例化一个UsernamePasswordAuthenticationToken对象,此时调用的是UsernamePasswordAuthenticationToken的两个参数的构造函数,如图构造方法一:构造方法一刚才(3)步骤中使用本类的有参构造方法创建了对象,其中super(null)调用的是父类的构造方法,传入的是权限集合,因为目前还没有认证通过,我们不知道有什么权限信息,所以这里设置为null,然后将用户名和密码分别赋值给principal和credentials,同样因为此时还未进行身份认证,所以给标记是否认证的字段进行赋值setAuthenticated(false)(记住这张图,后面我们还要用到构造方法二)

(4)setDetails(request, authRequest)是将当前的请求信息设置到authRequest中,包括ip、session等内容

(5)通过调用getAuthenticationManager()来获取认证管理器对象AuthenticationManager,然后调用他的实现类方法authenticate(Authentication authentication)。认证管理器对象本身不包含验证的逻辑,它的作用是用来管理AuthenticationProvider(存放在认证管理器接口的实现类内部的集合中)。

认证管理器接口的实现类ProviderManager实现了authenticate(Authentication authentication)方法,在该方法中遍历所有的AuthenticationProvider,再通过遍历到的每一个AuthenticationProvider对象的supports(Class<?> authentication)方法判断本provider是否是支持该Authentication类型(如UsernamePasswordAuthenticationToken)认证方式的provider。如果支持,调用该provider的authenticate方法进行认证。
如图代码所示
在这里插入图片描述
实际上authenticate的校验逻辑就写在了AbstractUserDetailsAuthenticationProvider抽象类中,首先去缓存中取UserDetails对象,如果取不到就调用retrieveUser方法来获取用户信息UserDetails调用了retrieveUser方法获取到了一个user对象,retrieveUser是一个抽象方法。
在这里插入图片描述
为什么AuthenticationProvider是一个集合去进行循环?是因为不同的登陆方式认证逻辑是不一样的,可能是微信等社交平台登陆,也可能是用户名密码登陆。AuthenticationManagerAuthenticationProvider收集起来,然后登陆的时候挨个去AuthenticationProvider中问你这种验证逻辑支不支持此次登陆的方式,根据传进来的Authentication类型会挑出一个适合的provider来进行校验处理。

然后去调用provider的验证方法authenticate方法,它是DaoAuthenticationProvider类中的一个方法,DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider

用户信息UserDetails是个接口,我们进入查看,它包含以下6个接口方法:

	Collection<? extends GrantedAuthority> getAuthorities();//获取权限集合
	String getPassword();  //获取密码
	String getUsername();   //获取用户名
	boolean isAccountNonExpired(); //账户未过期
	boolean isAccountNonLocked();   //账户未锁定
	boolean isCredentialsNonExpired(); //密码未过期
	boolean isEnabled();    //账户可用

查看它的继承关系发现User类实现了该接口,并实现了该接口的所有方法

DaoAuthenticationProvider实现了retrieveUser方法,在实现的方法中实例化了UserDetails对象。
在这里插入图片描述
也就是相当于自定义验证逻辑的那个类,去实现UserDetailService类,这个返回结果就是我们自己在数据库中根据username查询出来的用户信息。

AbstractUserDetailsAuthenticationProvider中如果没拿到信息就会抛出异常,如果查到了就回到AbstractUserDetailsAuthenticationProvider中去调用preAuthenticationChecks的check方法去进行预检查。
在这里插入图片描述
在预检查中进行了三个检查,因为UserDetail类中有四个布尔类型,去检查其中的三个,用户是否未锁定、用户是否未过期,用户是否不可用。
在这里插入图片描述
预检查之后紧接着去调用了additionalAuthenticationChecks方法去进行附加检查,这个方法也是一个抽象方法,在DaoAuthenticationProvider中去具体实现,在里面进行了加密解密去校验当前的密码是否匹配。
在这里插入图片描述
4.如果通过了预检查和附加检查,还会进行后检查,检查4个布尔值中的最后一个密码是否过期。
在这里插入图片描述

所有的检查都通过,则认为用户认证是成功的。

用户认证成功之后,会将这些认证信息和user传递进去,调用createSuccessAuthentication方法。在这个方法中同样会实例化一个user,但是这个方法不会调用之前传两个参数的函数,而是会调用三个参数的构造函数。这个时候,在调super的构造函数中不会再传null,会将authorities权限设进去,之后将用户密码设进去,最后setAuthenticated(true),代表验证已经通过,如上面第三张图中的构造方法二。
在这里插入图片描述

二、认证结果如何在多个请求之间共享

在验证成功之后,其中会调用AbstractAuthenticationFilter中的successfulAuthentication方法,在这个方法最后会调用我们自定义的successHandle登陆成功处理器,在调用这个方法之前会调用SecurityContextHolder.getContext()setAuthentication方法,会将我们验证成功的那个Authentication放到SecurityContext中,然后再放到SecurityContextHolder中。SecurityContextImpl中只是重写了hashcode方法和equals方法去保证Authentication的唯一。
在这里插入图片描述
SecurityContextHolder是ThreadLocal的一个封装,ThreadLocal是线程绑定的一个map,在同一个线程里在这个方法里往ThreadLocal里设置的变量是可以在另一个线程中读取到的。它是一个线程级的全局变量,在一个线程中操作ThreadLocal中的数据会影响另一个线程。也就是说创建成功之后,塞进去,此次登陆所有的请求都会通过SecurityContextPersisenceFilter去SecurityContextHolder拿那个Authentication。SecurityContextHolder在整个过滤器的最前面。

基本原理
当请求进来的时候,会先经过SecurityContextPersistenceFilter,SecurityContextPersistenceFilter会去session中去查SecurityContext的验证信息,如果有,就把SecurityContext的验证信息放到线程里直接返回回去,如果没有则通过,去通过其他的过滤器,当请求处理完回来之后,SecurityContextHolder会去检查当前线程中有没有SecurityContext的验证信息,如果有,则将SecurityContext放到session中。通过这样将不同的请求就可以从同一个session里拿到验证信息。

简单来说就是进来的时候检查session,有认证信息放到线程里。出去的时候检查线程,有认证信息放到session里。
因为整个请求和响应的过程都是在一个线程里去完成的,所以在线程的其他位置随时可以用SecurityContextHolder来拿到认证信息。

搬运的也差的不多,这些东西好像已经够用了,后面如果用到别的咱们再补充,告辞!

后记:
最近看到有大佬的博客讲得很好,大家有兴趣可以去看看
https://cloud.tencent.com/developer/article/1110641

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值