1. 用户认证处理流程原理
1.1 AuthenticationFilter
- 根据拦截链模型,不同的登录方式会有不同的AuthenticationFilter
- 如果使用的账号密码登录,将被
UsernamePasswordAuthenticationFilter
进行拦截 - 通过
UsernamePasswordAuthenticationFilter
的attemptAuthentication()
方法创建一个UsernamePasswordAuthenticationToken
对象
- UsernamePasswordAuthenticationFilter.java, 账号密码过滤器
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
return this.getAuthenticationManager().authenticate(authRequest);
}
- UsernamePasswordAuthenticationToken.java,创建对象
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
1.2 AuthenticationManager
AuthenticationManager
负责管理许多的AuthenticationProvider
,这些Provider可以针对不同的登陆方式提供不同的校验流程- 通过获取
ProvicerManager
,并调用其authenticate()
方法遍历所有的校验方式提供者,获取一个能够支持当前登录方式的校验器去进行校验 - 找到对应登录方式的Provider后将通过
provider.authenticate(authentication);
调用provider提供的校验方式进行校验
1.3 AuthenticationProvider
- 这里举例用了账号密码登录的方式,所以会使用实现了
AbstractUserDetailsAuthenticationProvider
的具体实现类DaoAuthenticationProvider
来执行校验; - 校验过程调用了
AbstractUserDetailsAuthenticationProvider
的authenticate
方法 - 校验过程分为三个阶段:
preAuthenticationChecks.check(user);
预检查,根据UserDetails接口中的四个方法,对账号是否启用、账号是否过期、账号是否锁定的状态进行检查additionalAuthenticationChecks();
,密码校验,该方法在是DaoAuthenticationProvider
中进行了实现,通过我们配置的PasswordEncoder进行密码校验postAuthenticationChecks.check(user);
,后校验,检验密码是否过期;
- 用户认证成功后,就会根据我们提供的UserDetails信息,执行
createSuccessAuthentication(principalToReturn, authentication, user);
方法; - 创建了一个新的
UsernamePasswordAuthenticationToken
对象,并以Authentication
的形式返回
- UsernamePasswordAuthenticationToken.java
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
1.4 返回AuthenticationFilter处理登录结果
Authentication
保存最终认证成功的用户认证对象,该对象将返回到最开始的拦截器当中- 最终返回拦截器链中的
AbstractAuthenticationProcessingFilter
,执行该过滤器的doFilter
方法 - 在doFilter中,执行
successfulAuthentication(request, response, chain, authResult);
来执行登录成功的处理器, successHandler.onAuthenticationSuccess(request, response, authResult);
登录成功处理器执行登陆成功的业务逻辑;- 如果在这个认证过程中出现失败都会抛出异常并被过滤器捕获,执行登录失败处理器
2.在多个请求中共享认证结果原理
- 在用户认证流程过程中,若认证成功的认证信息最后会传回到认证拦截器链当中,并在doFilter中执行
successfulAuthentication(request, response, chain, authResult);
,去调用我们自定义的认证成功处理器,在调用认证成功处理器之前有一句代码是将用户信息传入SecurityContext
的 SecurityContextHolder.getContext().setAuthentication(authResult);
- SecurityContext是用来包装Authentication对象而已
- SecurityContextHodler来获取容器保存用户认证信息,这里的SercurityContextHolder可以视为一个线程级别的变量对象,可被多个线程所共享;、
- 在拦截器链的最开始,有一个拦截器叫
SecurityContextPersistenceFilter
,它的作用有两个
- 请求进来时:检查Session,判断session中是否有SecurityContext(即有没有用户认证信息),如果有就将其拿出来,放到这个请求线程中;
- **请求完成返回时:**检查线程中是否有SecurityContext,将认证信息放到session中
3. 获取用户认证信息
- 方法一:
SecurityContextHolder.getContext().getAuthentication()
- 方法二:在控制器的入参中,声明Authentication类型,SpringMVC将自动给我们注入用户认证信息;
@GetMapping("/authentication")
public Authentication getUser(Authentication authentication){
return authentication;
}
@GetMapping("/userDetail")
public UserDetails getUser(@AuthenticationPrincipal UserDetails userDetails){
return userDetails;
}