RUOYI集成手机短信登录

2024-06-12 19:36

本文主要是介绍RUOYI集成手机短信登录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景:

工作过程中遇到需求,需要将短信验证码登录集成到RUOYI框架中。框架中使用的用户认证组件为Security,由于没有怎么研究过这个组件,这个功能不太会搞。所以这是一篇抄作业记录。参考文章如下

若依RuoYi整合短信验证码登录_若依短信登录-CSDN博客

任务需求描述请参考下面这篇文章

手机短信验证码登录-CSDN博客

需求分析:

根据需求描述,将需求分为以下几个工作任务:

1、需要有一个生成随机6位数验证码的方法

2、需要对接第三方平台将验证码发出去。(我这里对接的是阿里云)

3、对前端提供一个短信发送接口和手机号短信验证码登录接口

遇到的问题:

框架中只有提供用户名密码的登录认证方法,未提供手机号和验证码的认证方法。为前端提供的手机号短信验证码登录接口中无法使用用户名密码的这种认证方式。因此需要修改框架中的文件实现手机号和验证码的这种身份认证方法。

认证方法实现过程

想要实现新的身份认证方法就必须要搞明白Security这个组件的工作原理。参考如下文章

SpringBoot集成Spring Security(7)——认证流程_springboot整合springsecurity若依-CSDN博客

要实现整个过程必须要实现以下三个类

1、SmsCodeAuthenticationProvider 该类实现了AuthenticationProvider接口,用于实现自定义的认证逻辑

package com.ruoyi.framework.security.provider;import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.security.token.SmsCodeAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;public class SmsCodeAuthenticationProvider implements AuthenticationProvider {private UserDetailsService userDetailsService;public SmsCodeAuthenticationProvider() {}public SmsCodeAuthenticationProvider(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;String mobile = (String) authenticationToken.getPrincipal();String code = (String) authenticationToken.getCode();
//        checkSmsCode(mobile, code);UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities(), code);authenticationResult.setDetails(authenticationToken.getDetails());return authenticationResult;}private void checkSmsCode(String mobile, String code) {RedisCache redisCache = SpringUtils.getBean(RedisCache.class);String verifyKey = CacheConstants.SMS_CAPTCHA_CODE_KEY + mobile;String smsCode = redisCache.getCacheObject(verifyKey);if (smsCode == null) {throw new BadCredentialsException("验证码失效");}if (!code.equals(smsCode)) {throw new BadCredentialsException("验证码错误");}}@Overridepublic boolean supports(Class<?> authentication) {return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);}}

2、SmsCodeAuthenticationToken 该类继承了AbstractAuthenticationToken类,用来传递认证信息

package com.ruoyi.framework.security.token;import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;import java.util.Collection;public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;/*** 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,* 在这里就代表登录的手机号码*/private final Object principal;private final Object code;/*** 构建一个没有鉴权的 SmsCodeAuthenticationToken*/public SmsCodeAuthenticationToken(Object principal,Object code) {super(null);this.principal = principal;this.code = code;setAuthenticated(false);}/*** 构建拥有鉴权的 SmsCodeAuthenticationToken*/public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities, Object code) {super(authorities);this.principal = principal;this.code = code;// must use super, as we overridesuper.setAuthenticated(true);}@Overridepublic Object getCredentials() {return null;}@Overridepublic Object getPrincipal() {return this.principal;}public Object getCode() {return code;}@Overridepublic 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);}@Overridepublic void eraseCredentials() {super.eraseCredentials();}
}

3、SmsUserDetailsServiceImpl 该类实现了UserDetailsService接口,在SmsCodeAuthenticationProvider 类的的认证过程中通过UserDetailsService接口回调到这个实现类,用来查询并创建登录用户信息。注意这个service必须有个名字,此处是“userDetailsByPhonenumber”

package com.ruoyi.framework.web.service;import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;@Service("userDetailsByPhonenumber")
public class SmsUserDetailsServiceImpl implements UserDetailsService {private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);@Autowiredprivate ISysUserService userService;@Autowiredprivate SysPermissionService permissionService;@Overridepublic UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {SysUser user = userService.selectUserByUserName(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.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));}
}

还要对以下几个文件进行修改

1、SecurityConfig 位于ruoyi-framework模块下的config文件夹中,是Security组件的配置类

①注入认证的服务,@Qualifier中指定了服务接口的实现类

②配置身份认证接口

2、UserDetailsServiceImpl 该类是若依框架中账号密码认证的实现类,需要给这个service起一个名字,用来区分手机号验证码的实现类,并且@Primary这个注解是必须要加,不然系统无法启动

手机号短信验证码登录接口的实现

在SysLoginService中实现手机号验证码登录验证方法

/*** 手机号登录验证** @param mobile* @return*/public String smsLogin(String mobile,String code){// 用户验证Authentication authentication = null;try{SmsCodeAuthenticationToken authenticationToken = new SmsCodeAuthenticationToken(mobile,code);AuthenticationContextHolder.setContext(authenticationToken);// 该方法会去调用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(authenticationToken);}catch (Exception e){AsyncManager.me().execute(AsyncFactory.recordLogininfor(mobile, Constants.LOGIN_FAIL, e.getMessage()));throw new CustomException(e.getMessage());}finally{AuthenticationContextHolder.clearContext();}AsyncManager.me().execute(AsyncFactory.recordLogininfor(mobile, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());if (!soloLogin){// 如果用户不允许多终端同时登录,清除缓存信息String userIdKey = Constants.LOGIN_USERID_KEY + loginUser.getUser().getUserId();String userKey = redisCache.getCacheObject(userIdKey);if (StringUtils.isNotEmpty(userKey)){redisCache.deleteObject(userIdKey);redisCache.deleteObject(userKey);}}// 生成tokenreturn tokenService.createToken(loginUser);}

总结:

通过实现不同的provider可以实现不同的登录验证方式,例如邮箱登录验证和扫码登录验证等。不同的provider需要实现对应的token类,将认证信息传递到security组件中,provider的实现类中实现对认证信息的校验。UserDetailsService接口的实现类,用来查询用户在系统中的信息,一些数据状态的验证可以在此接口的实现类中实现验证逻辑。

这篇关于RUOYI集成手机短信登录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1055136

相关文章

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

springboot security验证码的登录实例

《springbootsecurity验证码的登录实例》:本文主要介绍springbootsecurity验证码的登录实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录前言代码示例引入依赖定义验证码生成器定义获取验证码及认证接口测试获取验证码登录总结前言在spring

springboot简单集成Security配置的教程

《springboot简单集成Security配置的教程》:本文主要介绍springboot简单集成Security配置的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录集成Security安全框架引入依赖编写配置类WebSecurityConfig(自定义资源权限规则

springboot集成Deepseek4j的项目实践

《springboot集成Deepseek4j的项目实践》本文主要介绍了springboot集成Deepseek4j的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录Deepseek4j快速开始Maven 依js赖基础配置基础使用示例1. 流式返回示例2. 进阶

Spring Boot 集成 Quartz 使用Cron 表达式实现定时任务

《SpringBoot集成Quartz使用Cron表达式实现定时任务》本文介绍了如何在SpringBoot项目中集成Quartz并使用Cron表达式进行任务调度,通过添加Quartz依赖、创... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启

最新Spring Security实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)

《最新SpringSecurity实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)》本章节介绍了如何通过SpringSecurity实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟... 目录前言改造准备开始登录页改造自定义用户名密码登陆成功失败跳转问题自定义登出前后端分离适配方案结语前言

Spring AI集成DeepSeek三步搞定Java智能应用的详细过程

《SpringAI集成DeepSeek三步搞定Java智能应用的详细过程》本文介绍了如何使用SpringAI集成DeepSeek,一个国内顶尖的多模态大模型,SpringAI提供了一套统一的接口,简... 目录DeepSeek 介绍Spring AI 是什么?Spring AI 的主要功能包括1、环境准备2

Spring AI集成DeepSeek实现流式输出的操作方法

《SpringAI集成DeepSeek实现流式输出的操作方法》本文介绍了如何在SpringBoot中使用Sse(Server-SentEvents)技术实现流式输出,后端使用SpringMVC中的S... 目录一、后端代码二、前端代码三、运行项目小天有话说题外话参考资料前面一篇文章我们实现了《Spring

Oracle登录时忘记用户名或密码该如何解决

《Oracle登录时忘记用户名或密码该如何解决》:本文主要介绍如何在Oracle12c中忘记用户名和密码时找回或重置用户账户信息,文中通过代码介绍的非常详细,对同样遇到这个问题的同学具有一定的参... 目录一、忘记账户:二、忘记密码:三、详细情况情况 1:1.1. 登录到数据库1.2. 查看当前用户信息1.