项目启动后会自动寻找UserDetailsService
实现类;
执行UserDetailsService
的唯一方法loadUserByName(String username)
并返回UserDetail
类,注意,返回的UserDetail
是根据用户名去数据库查询到用户信息;
拿到UserDetail
后会对UserDetail
进行一个预检查,检查用户是否存在,是否被锁定等等等;
全部认证成功后会调用AuthenticationSuccess
成功处理类,失败则调用AuthenticationFailHandler
类;
此时对于前后端分离项目而言,调用成功处理类,将验证结果返回给前端,前台拿到返回信息后,保存token致本地,然后每次请求都会拼接到head中。
认证处理流程
- 获取用户名与密码。
- 拿用户名与密码构建
UsernamePasswordAuthenticationToken
对象。 - 实例化
UsernamePasswordAuthenticationToken
之后调用了setDetails(request,authRequest)
将请求的信息设到UsernamePasswordAuthenticationToken
中去,包括ip、session等内。 - 然后去调用
AuthenticationManager
,AuthenticationManager
本身不包含验证的逻辑,它的作用是用来管理AuthenticationProvider
。
UsernamePasswordAuthenticationToken
对象其实是Authentication
接口的实现,Authentication
封装的是用户的验证信息。
- 用户名与密码设为本地变量
super((Collection)null);
中collection代表权限列表,在这传了一个null
进去是因为刚开始并没有进行认证,因此用户此时没有任何权限,并且设置没有认证的信息setAuthenticated(false)
;
校验逻辑
真正的校验逻辑写在AuthenticationProvider
中,
- 遍历
Provider
集合。因为不同的登陆方式认证逻辑是不一样的,可能是微信等社交平台登陆,也可能是用户名密码登陆。AuthenticationManager
其实是将AuthenticationProvider
收集起来,然后登陆的时候挨个去AuthenticationProvider
中问你这种验证逻辑支不支持此次登陆的方式,根据传进来的Authentication
类型会挑出一个适合的Provider
来进行校验处理。 - 挑一种
Provider
进行判断,调用Provider.supports()
方法,判断Provider
是否支持authentication.getClass()
的类型。 - 真正的执行校验逻辑。
provider.authenticate(authentication)
中,authenticate
是DaoAuthenticationProvider
类中的一个方法。
DaoAuthenticationProvider
继承了AbstractUserDetailsAuthenticationProvider
。实际上authenticate
的校验逻辑写在了AbstractUserDetailsAuthenticationProvider
抽象类中。
- 首先实例化
UserDetails
对象,调用了retrieveUser
方法获取到了一个user
对象,retrieveUser
是一个抽象方法。 - 如果没拿到信息就会抛出异常,如果查到了就会去调用
preAuthenticationChecks
的check(user)
方法去进行预检查。在预检查中进行了三个检查,因为UserDetail
类中有四个布尔类型,去检查其中的三个,用户是否锁定、用户是否过期,用户是否可用。 - 预检查之后紧接着去调用了
additionalAuthenticationChecks
方法去进行附加检查,这个方法也是一个抽象方法,检查密码是否匹配,在DaoAuthenticationProvider
中去具体实现,在里面进行了加密解密去校验当前的密码是否匹配。 - 如果通过了预检查和附加检查,还会进行厚检查,检查4个布尔中的最后一个,检查身份认证是否已过期。
- 所有的检查都通过,则认为用户认证是成功的。用户认证成功之后,会将这些认证信息和user传递进去,调用
createSuccessAuthentication
方法。
在这个方法中同样会实例化一个user,但是这个方法不会调用之前传两个参数的函数,而是会调用三个参数的构造函数。这个时候,在调super
的构造函数中不会再传null
,会将authorities
权限设进去,之后将用户密码设进去,最后setAuthenticated(true)
,代表验证已经通过。
最后创建一个authentication
会沿着验证的这条线返回回去。
认证成功
在拿到authentication
后,会调用successHandler.onAuthenticationSuccess(request, response, authResult);
,这个就是住在调用我们自己写的认证成功的处理器。
如果验证成功,则在这条路中调用我们系统的业务逻辑。
认证失败
如果在任何一处发生问题,就会抛出异常,在AbstractAuthenticationProcessingFilter中进行捕获,然后进行unsuccessfulAuthentication
,这里调用failureHandler.onAuthenticationFailure(request, response, failed);
,也就是调用我们自己定义的认证失败的处理器。
认证结果如何在多个请求之间共享
多个请求之间共享肯定是放在session中,但是它是什么时候,把什么东西放到了session中,什么时候在session中读出来。
什么时候共享
在验证成功最后会调用我们自定义的successHandler
登陆成功处理器,在调用这个方法之前会调用SecurityContextHolder.getContext().setAuthentication(authResult);
,会将我们验证成功的那个Authentication
放到SecurityContext
中,然后再放到SecurityContextHolder
中。
SecurityContextImpl
是SecurityContext的一个实现类,它包装了authentication
,重写了hashcode
方法和equals
方法去保证authentication
的唯一。
怎么共享
SecurityContextHolder
是ThreadLocal
的一个封装,ThreadLocal
是线程绑定的一个map,在同一个线程里在这个方法里往ThreadLocal
里设置的变量是可以在另一个线程中读取到的。可以理解为SecurityContextHolder
是一个线程级的全局变量,在一个线程中操作ThreadLocal
中的数据会影响另一个线程。
从setAuthentication(authResult)
,将authentication
放在当前的线程SecurityContextHolder
中去,在整个认证处理过程中,在任何一个方法里,通过SecurityContextHolder
的静态方法,都能讲authentication
读出来。
谁来用
登录成功后,登录所有的请求都会通过SecurityContextPersisenceFilter
去SecurityContextHolder
拿那个authentication
。SecurityContextPersisenceFilter
在整个过滤器的最前面。
SecurityContextPersisenceFilter
的作用:
- 当请求进入过滤器链时,先进它,检查
session
是否有securityContext
。如果有,就把从securityContext
中从session
取出,放到线程中。如果没有,则跳过。 - 当响应最后过它时,检查线程。如果线程中有
securityContext
,就发出来放到session
中去。
这样不同的请求,可以从线程中拿到相同的用户认证信息。整个请求与相应的过程都是在一个线程中完成的,因此在线程的其他位置,随时可以用SecurityContextHolder
来拿到用户认证信息。
获取认证用户信息
如何用
SecurityContextHolder
来拿到用户认证信息
可以使用SecurityContextHolder去获取用户的认证信息。1
2
3
4
5
6
7
8
9
10
11// UserController.java
4j
"/user") (
public class UserController {
"/me") (
public Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication();
}
}
可以直接获取authentication
。1
2
3
4
5
6
7
8
9
10
11// UserController.java
4j
"/user") (
public class UserController {
"/me") (
public Object getCurrentUser(Authentication authentication){
return authentication;
}
}
如果不需要返回全部的用户信息,可以使用@AuthenticationPrincipal
注解,返回部分用户信息。1
2
3
4
5
6
7
8
9
10
11// UserController.java
4j
"/user") (
public class UserController {
"/me") (
public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){
return userDetails;
}
}