Spring Security 认证执行流程

本文基于 Spring Security 5.xhtml

推荐阅读:

项目集成Spring Security数据库

SpringSecurity 整合 JWTjson

1、外层-正常登录调用

项目启动后会自动寻找 UserDetailsService 实现类;后端

执行 UserDetailsService 的惟一方法 loadUserByName(String username) 并返回 UserDetail 类,注意,返回的 UserDetail 是根据用户名去数据库查询到用户信息;ruby

拿到 UserDetail 后会对 UserDetail 进行一个预检查;微信

预检查啥?session

用户是否存在,是否被锁定等等等;app

所有认证成功后会调用 AuthenticationSuccess 成功处理类,失败则调用 AuthenticationFailHandler 类;前后端分离

此时对于先后端分离项目而言,调用成功处理类,一般是返回由 JWT 等生成的 token json 字符串,前台拿到返回信息后,保存 token 致本地,而后每次请求都会拼接到 head 中。ide

2、内层-源码级别

以访问某个项目中已有的连接为例:

http://localhost:7777/tmax/videoCategory/getAll

输入用户名、密码后点击登陆按钮,首先进入 UsernamePassworkAuthenticationFilter 的父类
AbstractAuthenticationProcessingFilter 调用 doFilter() 方法,而后再执行 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法进行验证;

UsernamePassworkAuthenticationFilter 类,顾名思义,表单登录过滤器,该类中重点是 attemptAuthentication() 方法:

该方法中经过 用户名+密码= 实例化一个 UsernamePasswordAuthenticationToken 的对象,做用是将用户请求的信息(用户名、密码、seeesion等)封装到该对象中,咱们点击进入该对象的构造器以下图所示:

须要说明一点的是,super((Collection)null); collection 表明权限列表,在这传了一个 null 进去是由于刚开始并无进行认证,所以用户此时没有任何权限,而且设置没有认证的信息 setAuthenticated(false)

再回到 UsernamePassworkAuthenticationFilter attemptAuthentication() 方法,能够看到方法最后调用了 getAuthenticationManager() 方法,而后就进入了 AuthenticationManager 接口的实现类 ProviderManager 中。

补充:AuthenticationManager 不包含验证用户名以及密码的功能,只是用来管理 AuthenticationProvider,全部的校验规则都是写在 AuthenticationProvider 中的;

继续走,在 ProviderManager 这个实现类中,它会调用AuthenticationProvider 接口的实现类获取用户的信息,用户的信息权限的验证就在该类中校验。

进入 ProviderManager 类后会调用 authenticate(Authentication authentication) 方法,它经过 AuthenticationProvider 实现类获取用户的登陆的方式,而后会有一个 while 迭代器模式的循环遍历,检查它是否支持这种登陆方式,具体的登陆方式有表单登陆,qq登陆,微信登陆等。若是最终都不支持会抛出相应的异常信息,若是支持则会进入AuthenticationProvider 接口的抽象实现类 AbstractUserDetailsAuthenticationProvider 中。

进入 AbstractUserDetailsAuthenticationProvider 类后会调用 authenticate(Authentication authentication) 方法对用户的身份进行校验,首先是判断用户是否为空,这个 user 是 UserDetail 的对象,若是为空,表示尚未认证,就须要调用 retrieveUser 方法去获取用户的信息,这个方法是抽象类 AbstractUserDetailsAuthenticationProvider 的扩展类DaoAuthenticationProvider 的一个方法。

在该扩展类的 retrieveUser 方法中调用 UserDetailsService 这个接口的实现类的 loadUserByUsername 方法去获取用户信息,而这里我本身编写了实现类 UserDetailsServiceImpl 类,在这个实现类中,咱们能够编写本身的逻辑,从数据库中获取用户密码等权限信息返回。

本地 UserDetailService 实现类 UserDetailsServiceImpl:

在拿到用户的信息后,返回到 AbstractUserDetailsAuthenticationProvider 类中调用 createSuccessAuthentication(principalToReturn, authentication, user) 方法,在该方法中会调用三个参数的UsernamePasswordAuthenticationToken 构造器,不一样于前面调用两个参数的,由于这里已经验证了用户的信息和权限,所以再也不是给父类构造器中传null 值了,而是用户的权限集合,而且设置认证经过setAuthenticated(true)

以下是 UsernamePasswordAuthenticationToken 构造器:

此时 authorities 再也不为空了。

在 UsernamePasswordAuthenticationToken 的父类中,它会检查用的权限,若是有一个为 null,表示权限没有相应的权限,抛出异常。

而后在 createSuccessAuthentication 方法返回后回到 ProvioderManager 的 authenticate 方法中返回 result,最后回到UsernamePasswordAuthenticationFilter 的刚开始进入的 attemptAuthentication 方法中返回。

attemptAuthentication() 方法中的返回,返回到哪?

再回到第一张图,UsernamePasswordAuthenticationFilter 父类 doFilter() 方法,返回值就是 authResult,若是过程当中发现存在异常则执行 unsuccessfulAuthentication.onAuthenticationFailure() 方法,若是认证成功则执行 successfulAuthentication.onAuthenticationSuccess() 方法,再结合上边提到的自定义 成功/失败处理类。

最后总结

流程大体是,首先进入 UsernamePasswordAuthenticationFilter 父类 AbstractAuthenticationProcessingFilter 执行 doFilter() 方法,这个 doFilter() 方法呢执行以下:

  1. 判断当前的 filter 是否能够处理当前请求,不能够的话则交给下一个 filter 处理;
  2. 抽象方法由子类 UsernamePasswordAuthenticationFilter 实现;
  3. 认证成功后,调用 sessionStrategy.onAuthentication() 处理一些与 session 相关的方法,最后再调用认证成功处理类,主要将当前的认证放到SecurityContextHolder中;
  4. 认证失败后则调用 认证失败处理类;

习惯在微信看技术文章,想要获取更多的Java资源的同窗,能够关注微信公众号:niceyoo

相关文章
相关标签/搜索