本篇为自定义验证码登录的下篇,主要会对自定义登录的流程进行讲解;我们曾在认证(二):表单登录的源码解析中对 Spring Security
的认证流程有过比较详细的学习。
“模仿不是创作,但创作不能不有模仿”————周谷城。
我们可以从表单登录认证流程中寻找灵感,定制化开发出验证码登录认证。
流程分析
首先先回顾一下表单登录的认证流程:
大致上有这么几个比较核心的类/接口:
-
UsernamePasswordAuthenticationFilter
过滤器,用以捕获表单登录请求。 -
UsernamePasswordAuthenticationToken
表单登录token
,用以匹配对应的认证Provider
。 -
AuthenticationManager
认证管理器,即不干活的"大佬"。 -
ProviderManager
管理领域的"二把手"。 -
DaoAuthenticationProvider
执行表单登录认证的Provider
,即真正干活的"小弟"。
分析得出,除了管理领域的“大佬” AuthenticationManager
和 “二把手” ProviderManager
不需要我们额外实现,剩下的我们都需要自定义实现。
基于这个结论,我大致描绘出验证码登录认证的流程图:
自定义验证码认证,需要将Filter
、Token
以及 Provider
嵌入到整个认证链路中;所以自定义认证需要做以下3件事情:
-
自定义
ValidateCodeAuthenticationFilter
,类比UsernamePasswordAuthenticationFilter
-
自定义
SmsValidateCodeAuthenticationToken
,类比UsernamePasswordAuthenticationToken
-
自定义
SmsValidateCodeAuthenticationProvider
,类比DaoAuthenticationProvider
流程实现
通过上面的流程分析,我们可以大致弄清楚需要做什么事情了;那么 Just Do it!!!
自定义ValidateCodeAuthenticationFilter
ValidateCodeAuthenticationFilter
拦截器的作用是:从请求中获取验证码进行验证。
可能有人会有疑问:从请求中获取验证码进行验证,听起来似乎是 Provider
做的事情呀,为什么会放在这里呢(类比表单登录的 Filter
只是做了请求参数到 Token
的转换,真实的校验是在 Provider
中)?
这里这么做是为了复用验证码的功能,在很多时候我们都用使用验证码的需求:修改密码、付款…… 如果将验证码的校验放在 Provider
模块的话,一定程度上限制了验证码只能用于登录。
ValidateCodeAuthenticationFilter.class
/**
* 验证码拦截器(自定义springSecurity过滤器实现,在登录之前进行验证码验证)
*
* InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,
* 凡是继承该接口的类,在初始化bean的时候都会执行该方法。
*
* @author 小奇
* @date 2020/10/08
**/
@Slf4j
@Component("ValidateCodeAuthenticationFilter")
public class ValidateCodeAuthenticationFilter extends OncePerRequestFilter implements InitializingBean {
/**
* key为请求的路径 map为验证码类型
* 存放所有需要校验验证码的url(例如:验证码登录、支付时需要填写验证码等等……)
*/
private Map<String, ValidateCodeEnum> urlMap = new HashMap<>(8);
/**
* 验证请求url与配置的url是否匹配的工具类
*/
private AntPathMatcher pathMatcher = new AntPathMatcher();
/**
* 通过配置文件的方式配置 一些需要验证码认证的url
*/
@Autowired
private SecurityProperties securityProperties;
/**
* 认证失败处理器
*/
@Autowired
private WebAuthenticationFailHandler failureHandler;
/**
* Holder是对Processor的更上一级调用
*/
@Autowired
private ValidateCodeProcessorHolder validateCodeProcessorHolder;
/**
* 初始化本类的Bean对象时候,会执行该方法进行相应的初始化
*/
@Override
public void afterPropertiesSet() {
initUrlMap(securityProperties.getCode().getImage().getUrls(), ValidateCodeEnum.IMAGE);
initUrlMap(securityProperties.getCode().getSms().getUrls(), ValidateCodeEnum.SMS);
}
/**
* 拦截 校验验证码
*
* @param request
* @param response