流程图:如下。
1.密码登录请求给UsernamePasswordAuthenticationFilter,此过滤器将用户名和密码拿出来组装成UsernamePsswordAuthenticationToken(未认证)对象。
查看继承图可以看到它是Authentication接口的一个实现,Authentication
里面封装了用户的认证信息
这时调用的构造函数,将用户名密码设为本地变量principal
,credentials
,super
调用父类的构造函数,因为此时并不知道用户有什么权限,所以为null
创建好UsernamePasswordAuthenticationToken
对象之后,同时将请求信息也封装进去进入到getAuthenticationManager().authenticate(authRequest);
getAuthenticationManager()
方法返回一个AuthenticationManager
接口的实现类,跟进去authenticate()
方法
这是AuthenticationManager的一个实现类ProviderManager
在这个方法中,循环找出一个AuthenticationProvider
的实现类,来调用实现类的authenticate
方法来进行认证,不同的登陆方式所需要的AuthenticationProvider
的实现类不同,所以这里要循环找出适合当前登陆方式的provider
,这里使用的是DaoAuthenticationProvider
这里需要注意的是,如果是选用的短信认证登录的话,这里选择的是我们自己写的SmsCodeAuthenticationProvider
进入authenticate
方法,首先来到的是DaoAuthenticationProvider
的抽象父类
进入retrieveUser()方法:
在这里会拿到UserDetailsService
的具体实现类,执行loadUserByUsername()方
法
查看this.getUserDetailsService()
,可以看到返回的就是我们自己写的实现类:UserLoginService
,然后执行loadUserByUsername()
方法返回User对象(实现了UserDetails
接口的)。通过上面的方法调用下面自己写的UserLoginService,来获取用户信息。
然后通过下面的方法去判定用户是否锁定,是否可用,是否过期。
通过下面的方法判断rawPassword(表单输入的密码)和存入数据库中加密之后的密码(encodePassword)进行判定。
然后先经过下图的代码段1处后,在到代码段2处。经过这次调用的是UsernamePasswordAuthenticationToken
三个参数的构造器,因为此时已经拿到了认证信息authorities
,super.setAuthenticated(true)
已经认证
这时AbstractUserDetailsAuthenticationProvider
的authenticate方法,执行
createSuccessAuthentication
返回一个经过认证的Authentication对象。由下图的3处可以看到已经认证成功
一步一步返回到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获取用户信息:
@RestController
public class SecurityContextHolderController {
@GetMapping("getUserMsg")
public Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication();
}
}
登陆之后访问接口: