文章目录
为了省去与本篇主题无关的代码,短信验证码只是一个模拟。如果你需要具体的实际例子,在下面的源码链接中除了包括每一章的代码外,还包括从头到尾的完整整合代码,方便大家参考学习。
一、理论说明
在开始编码前,先理解下短信验证码的实现流程。如果你能对《Spring Security认证过程》这篇文章有一定的了解的话,那么这篇文章的学习你会轻松许多。
1.1 用户名密码登录逻辑
废话不多说,在上一篇文章中,以标准的用户名密码登录为例,讲解了整个认证流程。大致流程如下:
先进入 UsernamePasswordAuthenticationFilter
中,根据输入的用户名和密码信息,构造出一个暂时没有鉴权的 UsernamePasswordAuthenticationToken
,并将 UsernamePasswordAuthenticationToken
交给 AuthenticationManager
处理。
AuthenticationManager
本身并不做验证处理,他通过 for-each 遍历找到符合当前登录方式的一个 AuthenticationProvider
,并交给它进行验证处理,对于用户名密码登录方式,这个 Provider 就是 DaoAuthenticationProvider
。
在这个 Provider
中进行一系列的验证处理,如果验证通过,就会重新构造一个添加了鉴权的 UsernamePasswordAuthenticationToken
,并将这个 token 传回到 UsernamePasswordAuthenticationFilter
中。
在该 Filter 的父类 AbstractAuthenticationProcessingFilter
中,会根据上一步验证的结果,跳转到 successHandler
或者是failureHandler
。
1.2 短信验证码登录逻辑
我们可以仿照用户名密码登录的逻辑,来实现短信验证码的登陆逻辑。
用户名密码登录有个 UsernamePasswordAuthenticationFilter
,我们搞一个 SmsAuthenticationFilter
,代码粘过来改一改。
用户名密码登录需要 UsernamePasswordAuthenticationToken
,我们搞一个 SmsAuthenticationToken
,代码粘过来改一改。
用户名密码登录需要 DaoAuthenticationProvider
,我们模仿它也 implenments AuthenticationProvider
,叫做 SmsAuthenticationProvider
。
我们自己搞了上面三个类以后,想要实现的效果如上图所示。当我们使用短信验证码登录的时候:
- 先经过
SmsAuthenticationFilter
,构造一个没有鉴权的SmsAuthenticationToken
,然后交给
AuthenticationManager
处理。 AuthenticationManager
通过 for-each 挑选出一个合适的provider
进行处理,当然我们希望这个
provider
要是SmsAuthenticationProvider
。- 验证通过后,重新构造一个有鉴权的
SmsAuthenticationToken
,并返回给
SmsAuthenticationFilter
。
4.filter
根据上一步的验证结果,跳转到成功或者失败的处理逻辑。
二、代码实战
2.1 SmsAuthenticationToken
首先我们编写 SmsAuthenticationToken
,这里直接参考 UsernamePasswordAuthenticationToken
源码,直接粘过来,改一改
步骤:
principal
原本代表用户名,这里改成mobile
,代表了手机号码。- credentials 原本代码密码,短信登录用不到,直接删掉。
SmsCodeAuthenticationToken()
两个构造方法一个是构造没有鉴权的,一个是构造有鉴权的。- 剩下的几个方法去除无用属性即可。
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
/**
* 短信登录 AuthenticationToken,模仿 UsernamePasswordAuthenticationToken 实现
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
* 在这里就代表登录的手机号码
*/
private final Object principal; //存放认证信息。
/**
* 构建一个没有鉴权的 SmsCodeAuthenticationToken
*/
//mobile:表示手机号。
public SmsCodeAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
/**
* 构建拥有鉴权的 SmsCodeAuthenticationToken
*/
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
2.2 SmsAuthenticationFilter
然后编写 SmsAuthenticationFilter
,参考 UsernamePasswordAuthenticationFilter
的源码,直接粘过来,改一改。
步骤:
- 认证请求的方法必须为POST
- 从request中获取手机号
- 封装成自己的
Authenticaiton
的实现类SmsCodeAuthenticationToken(未认证)
- 调用
AuthenticationManager
</