前言
在上一篇文章《Spring Security实现用户名或者手机号登录》中,通过自定义实现UserDetailsService接口,实现了同时支持用户名+密码或者手机号+密码登录的问题。
实际场景中,用户出了忘记用户名之外,忘记密码也很常见。使用手机动态验证码登录网站越来越流行。
原理分析
在Spring Security中,密码验证属于鉴权的一部分,主要由AuthenticationProvider
的实现类来实现用户名和密码的匹配校验。
通过debug源码可以发现,项目中使用Spring的AbstractUserDetailsAuthenticationProvider
的实现类,在apublic Authentication authenticate(Authentication authentication)
方法中进行校验工作。
由于我们需要支持手机验证码登录,那么很显然,我们主要写一个自己的实现类,然后重写public Authentication authenticate(Authentication authentication)
方法。
主要代码
AuthenticationProvider
实现类
@Component
public class CustAuthenticationProvider implements AuthenticationProvider {
@Autowired
UserDetailsService userDetailsService; //主要用来检查用户名
@Autowired
CustBCryptPasswordEncoder passwordEncoder; //主要用来比对密码
@Autowired
SmsSendRecordService smsSendRecordService; //短信验证码服务
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails userDetails;
//检查用户名有效性
try {
userDetails = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
throw new BadCredentialsException(MessageConstant.USERNAME_NOT_FOUND);
}
//优先匹配密码
if (passwordEncoder.matches(password, userDetails.getPassword())) {
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
return new UsernamePasswordAuthenticationToken(userDetails, password, authorities);
} else {
//这里将password尝试作为手机验证码,然后和已发送的验证码检验,需要注意验证码有效期,是否已验证码等判断
return new UsernamePasswordAuthenticationToken(userDetails, password, authorities);
}
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}