前言
Spring security的登录和注销我们大概都了解了,然后核心类我们也了解了,接下来呢还有基础的,quick start我们也做了。就缺少一次成体系的流程介绍, 本篇将会以角色扮演的形式带你深入源码了解spring security底层是怎么认证的。
认证
是什么?
小白: "认证认证到底什么是认证呢?是直接比较密码吗?"
小黑: "认证的核心就是比较用户输入的账户和数据库中已经注册过的账户的密码是否相等。根据相等的结果,才能给予用户后续一系列授权和访问操作。"
小白: "那spring security的认证是和有什么区别吗?"
小黑: 总体来看的话基本上没什么区别,说白了就是用户输入的账户和数据库中的账户进行比较就这么简单。如果细分下来的话就有下列几个步骤:
-
第一从客户端请求中拿到用户输入的账户和密码,
-
第二从数据库中拿到数据库中的账户和密码,
-
第三就是做比较。
-
最后保存认证成功的信息。
这是论证主要的步骤当然还有其他功能。比如说remember me功能之类。
小白: "那看起来还挺简单的,那spring security源码也是这么搞的吗?"
小黑: "万变不离其宗,接下来我们将以上面总结的这几个步骤进行分析,spring security源码中的这些步骤所处的源码到底是怎么实现的"
小黑: "不过在分析源码之前,需要罗列一下过滤器执行顺序的列表。"
Spring security中过滤器列表
小黑: "这些过滤器不需要记得很详细,只需要在关键时刻拿出来看看就行了。"
开始分析
直接从上图的过滤器开始分析,可以省掉很多事情。
UsernamePasswordAuthenticationFilter看看这个类的类族:
经常分析源码的人应该知道, 接口对于java程序员而言是类似于功能, 我们添加的每一个接口就是一个功能, 所以我们可以看看
UsernamePasswordAuthenticationFilter类都有哪些功能
我们可以直接看到它其实就是一个过滤器,而且是servlet的过滤器。
紧接着我们知道, 如果使用类, 最好使用类的接口, 不然不好扩展, 因为使用接口的话, 我们可以
new各种子类
那么就可以找到doFilter的函数在这里AbstractAuthenticationProcessingFilter
-
第一从客户端请求中拿到用户输入的账户和密码,
-
第二从数据库中拿到数据库中的账户和密码,
-
第三就是做比较。
-
最后保存认证成功的信息。
小白: "简直和我们说的过程一致, 完美"
从前端拿到账户和密码
这个过程主要在这里UsernamePasswordAuthenticationFilter#attemptAuthentication
看看, 下面都是通过 request 获取的 username 和 password 的源码
如果需要进行前后端分离, 那么这两个函数可以修改哦, 当然你也可以使用在
attemptAuthentication中获取usernamepassword
从数据库中拿账户密码和密码匹配比较
这里需要进入下面源码才能看到上面的账户密码
this.getAuthenticationManager().authenticate(authRequest);
进入上面的那个函数只能看到ProviderManager, 就能够看到前面一章节熟悉的
AuthenticationManager 和 AuthenticationManager.parent 之间的关系, 父子AuthenticationManager关系
紧接着才会讲到我们需要的函数
AbstractUserDetailsAuthenticationProvider#authenticate
保存认证成功信息
AbstractAuthenticationProcessingFilter#successfulAuthentication
最后在附上绝杀流程图
总结
-
UsernamePasswordAuthenticationFilter的作用-
获取用户名和密码
-
配置匹配器
-
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST"); -
前端表单的username和password也可以在这里改
-
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
-
-
AbstractAuthenticationProcessingFilter的作用是requiresAuthentication: 进行匹配器调用, 把一些不满足的请求过滤掉attemptAuthentication: 从前端获取用户名和密码, 并且进行认证请求this.getAuthenticationManager().authenticate(authRequest);onAuthentication: 认证成功后进行一些关于session的操作, 比如保存session内的部分信息或者验证session中的一些参数等等successfulAuthentication: 做一些认证成功之后的操作, 比如保存认证成功信息到SecurityContextHolder上下文, 比如remember-me记住我功能等等InteractiveAuthenticationSuccessEvent事件, 使用Spring Event发送的事件, 意味着认证和登录成功, 并且会发送一个Authentication, 里面保存了认证信息
-
重写
UsernamePasswordAuthenticationFilter我们可以做很多事情, 比如原文说的前后端分离, 比如图片验证码功能等 -
AuthenticationManager真正执行认证的过程, 而UsernamePasswordAuthenticationFilter主要的任务是拿到usernamepassword并且调用AuthenticationManager进行认证, 通常这个类都是ProviderManager -
ProviderManager: 这个类你就认为是一个集合, 里面遍历循环private List<AuthenticationProvider> providers- 它还有
parent字段 - 可以在里面进行敏感数据删除
eraseCredentials
- 它还有
-
AuthenticationProvider: 真正执行认证业务的类, 如果是表单认证方式, 他会调用DaoAuthenticationProvider这里我们可以看出Provider的运行模式是support判断是否支持, 然后再执行业务 -
DaoAuthenticationProvider: 这个类的功能additionalAuthenticationChecks: 判断密码是否相等retrieveUser: 从数据库中读取账户UserDetails, 如果你需要自定义那么需要重写UserDetailsService这个接口, 内容主要是从数据库中根据用户名读取用户名和密码