首先进入的是UsernamePasswordAuthenticationFilter
这个过滤器,拿到登陆的用户名和密码生成一个UsernamePasswordAuthenticationToken
的对象,查看继承图可以看到它是Authentication
接口的一个实现,Authentication
里面封装了用户的认证信息
这时调用的构造函数,将用户名密码设为本地变量principal
,credentials
,super调用父类的构造函数,因为此时并不知道用户有什么权限,所以为null
同样此时的身份信息也没有经过认证:setAuthenticated(false);
创建好UsernamePasswordAuthenticationToken
对象之后,同时将请求信息也封装进去进入到getAuthenticationManager().authenticate(authRequest);
getAuthenticationManager()
方法返回一个AuthenticationManager
接口的实现类,跟进去authenticate()
方法:
这是AuthenticationManager
的一个实现类ProviderManager
在这个方法中,循环找出一个AuthenticationProvider
的实现类,来调用实现类的authenticate
方法来进行认证,不同的登陆方式所需要的AuthenticationProvider
的实现类不同,所以这里要循环找出适合当前登陆方式的provider
,这里使用的是DaoAuthenticationProvider
进入authenticate
方法,首先来到的是DaoAuthenticationProvider
的抽象父类
进入retrieveUser()
方法:
在这里会拿到UserDetailsService
的具体实现类,执行loadUserByUsername()
方法
查看this.getUserDetailsService()
,可以看到返回的就是我们自己写的实现类:UserLoginService
,然后执行loadUserByUsername()
方法返回User
对象(实现了UserDetails接口的)
在AbstractUserDetailsAuthenticationProvider
拿到返回的用户信息后,执行preAuthenticationChecks.check
方法:检查三个布尔值认证信息
再执行additionalAuthenticationChecks
,具体方法再其子类DaoAuthenticationProvider
中实现,这里使用了passwordEncoder
校验用户的密码,密码不匹配就抛出异常
接下来执行的是postAuthenticationChecks.check(user);
,校验用户密码是否没有过期
所有认证完成后
这次调用的是UsernamePasswordAuthenticationToken
三个参数的构造器,因为此时已经拿到了认证信息authorities
,super.setAuthenticated(true)
已经认证
这时AbstractUserDetailsAuthenticationProvider
的authenticate
方法,执行
createSuccessAuthentication
返回一个经过认证的Authentication
对象一步一步返回到UsernamePasswordAuthenticationFilter
然后会经过一个AbstractAuthenticationProcessingFilter
,在这个抽象类中执行doFilter()
方法
成功就执行成功的处理器,失败则执行失败的处理器
来到我们自己写的登陆成功的处理器
执行到这里,已经登陆成功,相应的登陆信息也返回给登陆请求。
下面看看不同的请求是如何拿到认证信息的(认证结果在不同的请求之间共享):
在AbstractAuthenticationProcessingFilter
中可以看到,在执行成功处理器之前,会执行SecurityContextHolder.getContext().setAuthentication(authResult);
这个authResult
就是包含了已登陆的用户授权信息的Authentication
对象
SecurityContextHolder
:Associates a given SecurityContext with the current execution thread.(将给定的SecurityContext与当前执行线程关联。)
SecurityContext
:安全上下文,即存储认证授权的相关信息,实际上就是存储"当前用户"账号信息和相关权限。这个接口只有两个方法,获取和设置Authentication对象的getter、setter方法。
SecurityContextHolder使用了ThreadLocal机制来保存每个使用者的安全上下文。这意味着,只要针对某个使用者的逻辑执行都是在同一个线程中进行,即使不在各个方法之间以参数的形式传递其安全上下文,各个方法也能通过SecurityContextHolder工具获取到该安全上下文。SecurityContextPersistenceFilter
是拦截链中的第一个拦截器,当一次请求结束时,这个过滤器会看当前线程上是否保存有SecurityContext,如果有则会被存到session中,sessionId被设置到cookie中,登陆之后的其他请求到来时,通过sessionId可以拿到session,把SecurityContext拿出来通过SecurityContextHolder放到当前线程中,从而在整个请求的流程中都可以拿到用户信息。
SecurityContextHolder获取用户信息:
/**
* @auther Mr.Liao
* @date 2019/8/20 9:38
*/
@RestController
public class SecurityContextHolderController {
@GetMapping("getUserMsg")
public Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication();
}
}
登陆之后访问接口:
也可以直接将Authentication对象作为方法的参数,在方法中使用