若依系统中增加短信登录
若依系统中增加短信登录
首先需要明白AuthenticationManager和UserDetailsService的关系:
https://blog.csdn.net/feng905001561/article/details/119868411
了解过后就开始编写短信登录所需要的东西,基本上都是仿着账号密码登录的东西去写的
添加用户验证所执行的方法
@Service
public class SmsUserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(SmsUserDetailsServiceImpl.class);
@Autowired
private ISysUserService userService;
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException
{
// 根据手机号查询用户的方法
SysUser user = userService.selectUserByPhonenumber(phone);
if (StringUtils.isNull(user))
{
log.info("登录手机号:{} 不存在.", phone);
throw new UsernameNotFoundException("登录手机号:" + phone + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", phone);
throw new BaseException("对不起,您的账号:" + phone + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", phone);
throw new BaseException("对不起,您的账号:" + phone + " 已停用");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user, permissionService.getMenuPermission(user));
}
}
添加短信登录的鉴权过滤器
/**
* 短信登录的鉴权过滤器,模仿 UsernamePasswordAuthenticationFilter 实现
*/
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* 手机号码的字段name
*/
public static final String SPRING_SECURITY_FORM_PHONE_KEY = "phonenumber";
private String phoneParameter = SPRING_SECURITY_FORM_PHONE_KEY;
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/smsLogin", "POST");
/**
* 是否仅 POST 方式
*/
private boolean postOnly = true;
public SmsAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public SmsAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String phone = obtainPhone(request);
if (phone == null) {
phone = "";
}
phone = phone.trim();
SmsAuthenticationToken authRequest = new SmsAuthenticationToken(phone);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
@Nullable
protected String obtainPhone(HttpServletRequest request) {
return request.getParameter(phoneParameter);
}
protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public String getPhoneParameter() {
return phoneParameter;
}
public void setPhoneParameter(String phoneParameter) {
Assert.hasText(phoneParameter, "Phone parameter must not be empty or null");
this.phoneParameter = phoneParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
}
添加短信认证Token
/**
* 短信登录 AuthenticationToken,模仿 UsernamePasswordAuthenticationToken 实现
*/
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
* 在这里就代表登录的手机号码
*/
private final Object principal;
public SmsAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
super.setAuthenticated(true);
}
public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
添加短信登录的鉴权认证器
/**
* 短信登录的鉴权认证器,模仿 AbstractUserDetailsAuthenticationProvider 实现
*/
public class SmsAuthenticationProvider implements AuthenticationProvider {
protected final Log logger = LogFactory.getLog(this.getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private SmsUserDetailsServiceImpl userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(SmsAuthenticationToken.class, authentication, () ->
this.messages.getMessage("AbstractSmsCodeAuthenticationProvider.onlySupports", "Only SmsCodeAuthenticationToken is supported"));
SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication;
// 手机号
String phone = (String) authenticationToken.getPrincipal();
// 查询用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
this.logger.debug("Authenticated user");
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
// 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(SmsUserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
添加短信认证配置
/**
* 短信认证配置
*/
@Component
public class SmsAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private SmsUserDetailsServiceImpl userDetailsService;
@Override
public void configure(HttpSecurity http) {
// 实例化认证器
SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
// 设置认证器 SmsUserDetailsServiceImpl
smsAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(smsAuthenticationProvider);
}
}
在SecurityConfig中使用改配置,并放行你的登录方法
添加短信登录的方法
/**
* 短信登录
*
* @param phonenumber 手机号
* @param code 验证码
* @return 结果
*/
public String smsLogin(String phonenumber, String code) {
// 用户验证
Authentication authentication;
// 用户信息
LoginUser loginUser;
try {
// 该方法会去调用SmsUserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(new SmsAuthenticationToken(phonenumber));
loginUser = (LoginUser) authentication.getPrincipal();
// 验证码校验
checkSms(phonenumber, code);
} catch (Exception e) {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
// 记录登录信息
recordLoginInfo(loginUser.getUserId());
// 删除验证码缓存
redisCache.deleteObject(String.format("sms_captcha_code:%s", phonenumber));
// 生成token
return tokenService.createToken(loginUser);
}
/**
* 检查手机号登录
*
* @param phone 手机号
* @param code 验证码
*/
private void checkSms(String phone, String code) {
String verifyKey = String.format("sms_captcha_code:%s", phone);
String smsCode = redisCache.getCacheObject(verifyKey);
if (StringUtils.isEmpty(code)) {
throw new BadCredentialsException("验证码不能为空");
}
if (StringUtils.isEmpty(smsCode)) {
throw new BadCredentialsException("验证码失效");
}
if (!code.equals(smsCode)) {
throw new BadCredentialsException("验证码错误");
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)