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集成easypoi导出word换行处理过程

《springboot集成easypoi导出word换行处理过程》SpringBoot集成Easypoi导出Word时,换行符n失效显示为空格,解决方法包括生成段落或替换模板中n为回车,同时需确... 目录项目场景问题描述解决方案第一种:生成段落的方式第二种:替换模板的情况,换行符替换成回车总结项目场景s

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

springboot2.1.3 hystrix集成及hystrix-dashboard监控详解

《springboot2.1.3hystrix集成及hystrix-dashboard监控详解》Hystrix是Netflix开源的微服务容错工具,通过线程池隔离和熔断机制防止服务崩溃,支持降级、监... 目录Hystrix是Netflix开源技术www.chinasem.cn栈中的又一员猛将Hystrix熔

MyBatis-Plus 与 Spring Boot 集成原理实战示例

《MyBatis-Plus与SpringBoot集成原理实战示例》MyBatis-Plus通过自动配置与核心组件集成SpringBoot实现零配置,提供分页、逻辑删除等插件化功能,增强MyBa... 目录 一、MyBATis-Plus 简介 二、集成方式(Spring Boot)1. 引入依赖 三、核心机制

SpringBoot集成P6Spy的实现示例

《SpringBoot集成P6Spy的实现示例》本文主要介绍了SpringBoot集成P6Spy的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录本节目标P6Spy简介抛出问题集成P6Spy1. SpringBoot三板斧之加入依赖2. 修改

Spring Security重写AuthenticationManager实现账号密码登录或者手机号码登录

《SpringSecurity重写AuthenticationManager实现账号密码登录或者手机号码登录》本文主要介绍了SpringSecurity重写AuthenticationManage... 目录一、创建自定义认证提供者CustomAuthenticationProvider二、创建认证业务Us

Springboot项目登录校验功能实现

《Springboot项目登录校验功能实现》本文介绍了Web登录校验的重要性,对比了Cookie、Session和JWT三种会话技术,分析其优缺点,并讲解了过滤器与拦截器的统一拦截方案,推荐使用JWT... 目录引言一、登录校验的基本概念二、HTTP协议的无状态性三、会话跟android踪技术1. Cook

springboot项目中集成shiro+jwt完整实例代码

《springboot项目中集成shiro+jwt完整实例代码》本文详细介绍如何在项目中集成Shiro和JWT,实现用户登录校验、token携带及接口权限管理,涉及自定义Realm、ModularRe... 目录简介目的需要的jar集成过程1.配置shiro2.创建自定义Realm2.1 LoginReal