RUOYI集成手机短信登录

news2024/11/27 12:38:10

背景:

工作过程中遇到需求,需要将短信验证码登录集成到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;
    }

    @Override
    public 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("验证码错误");
        }
    }


    @Override
    public 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 override
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    public Object getCode() {
        return code;
    }

    @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();
    }
}

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);

    @Autowired
    private ISysUserService userService;

    @Autowired
    private SysPermissionService permissionService;


    @Override
    public 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.loadUserByUsername
            authentication = 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);
            }
        }
        // 生成token
        return tokenService.createToken(loginUser);
    }

总结:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1816322.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++和C语言到底有什么区别?

引言&#xff1a;C和C语言是两种非常常见的编程语言&#xff0c;由于其广泛的应用和灵活性&#xff0c;它们在计算机科学领域内受到了广泛的关注。虽然C是从C语言发展而来的&#xff0c;但是这两种语言在许多方面都有所不同。本文将对C和C语言进行比较和分析&#xff0c;以便更…

汇聚荣科技有限公司实力强吗?

汇聚荣科技有限公司实力强吗?在当今快速发展的科技行业中&#xff0c;公司的实力往往决定了其市场竞争力和发展前景。对于汇聚荣科技有限公司而言&#xff0c;其是否具备强大的实力&#xff0c;不仅关系到自身的发展&#xff0c;也影响着投资者和合作伙伴的选择。因此&#xf…

CAD2022下载与安装

CAD2022下载与安装 安装包下载安装包解压缩软件安装安装完成 安装包下载 安装包下载链接&#xff1a; https://pan.xunlei.com/s/VNyyAVUev-7XHig_2VIGiTN1 提取码&#xff1a;mxt8 下载安装包&#xff0c;完成后&#xff0c;得到一个压缩文件 安装包解压缩 右键解压到当前…

深圳建网站

深圳是中国最具活力和创新力的城市之一&#xff0c;也是全球网站建设行业蓬勃发展的重要市场之一。随着信息科技的不断发展和互联网的普及&#xff0c;越来越多的企业和个人意识到了建立网站的重要性&#xff0c;通过网站可以为企业带来更多的业务机会和营销渠道。 建立一个优质…

23种设计模式之组合模式

组合模式 1、定义 组合模式&#xff1a;组合多个对象形成树状结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象 2、组合模式结构 Component&#xff08;抽象构件&#xff09;&#xff1a;可以是接口或抽象类&#xff0c;为叶子构件…

【Unity】RPG2D龙城纷争(二)关卡、地块

更新日期&#xff1a;2024年6月12日。 项目源码&#xff1a;在第四章发布 索引 简介地块&#xff08;Block&#xff09;一、定义地块类二、地块类型三、地块渲染四、地块索引 关卡&#xff08;Level&#xff09;一、定义关卡类二、关卡基础属性三、地块集合四、关卡初始化五、关…

CorelDRAW 2024开启设计新纪元,终身永久版与中文破解版的全面解析及安装攻略

当我们谈论图形设计软件时&#xff0c;CorelDRAW无疑是一个响亮的名字。作为一款强大的矢量图形编辑工具&#xff0c;它以其丰富的功能和用户友好的界面赢得了全球设计师的喜爱。随着CorelDRAW 2024的发布&#xff0c;这个备受瞩目的版本带来了前所未有的创新特性&#xff0c;进…

Linux基础IO【II】

今天&#xff0c;我们接着在上一篇文章的基础上&#xff0c;继续学习基础IO。观看本文章之前&#xff0c;建议先看&#xff1a;Linux基础IO【I】&#xff0c;那&#xff0c;我们就开始吧&#xff01; 一.文件描述符 1.重新理解文件 文件操作的本质&#xff1a;进程和被打开文件…

【网络编程】套接字类型与协议设置

协议&#xff1a;计算机对话的通信规则&#xff0c;简而言之是为了完成数据交换而定好的约定。 #include<sys/socket.h> int socket(int domain, int type,int protocol); //domian使用的协议族信息&#xff0c;type传输类型&#xff0c;protocol计算机间通信使用的协议…

苹果WWDC重磅发布的IOS 18、Apple Intelligence背后的技术分析!

2024年6月10日&#xff0c;在2024年WWDC全球开发者大会上&#xff0c;苹果推出了Apple Intelligence&#xff0c;这是深度集成到iOS 18、iPadOS 18和macOS Sequoia中的个人智能系统。 为了让大模型能在 iPhone 端侧跑&#xff0c;苹果还是做了很多事情的。接下来就跟大家介绍一…

使用Java进行网络采集:代理IP与参数传递详解

在Java编程语言中&#xff0c;参数传递机制是一个常见的讨论话题。理解这一点对于编写高效且无错误的Java代码至关重要。本文将探讨Java的参数传递机制&#xff0c;解析其究竟是“按引用传递”还是“按值传递”&#xff0c;并结合网络爬虫技术的实例&#xff0c;展示如何在实际…

7 款便捷好用的AI API

AI API&#xff08;人工智能应用程序接口&#xff09;是一种软件中间件&#xff0c;它允许开发者和企业通过编程方式访问和集成人工智能服务到他们自己的应用程序、网站或系统中。AI API通常由专业的AI服务提供商开发和维护&#xff0c;使得用户无需深入了解复杂的AI算法和模型…

UML相关1

汽车租赁系统中的用例图简述(10分) 本系统根据功能可以分为三个用例图&#xff1a; 客户用例图&#xff1a;主要描述客户注册、登录、找回密码、查询车辆信息&#xff08;包括所有车辆信息、已借车辆信息、租赁历史信息&#xff09;、修改个人信息、网上预订车辆、电话预定车…

【强化学习】gymnasium自定义环境并封装学习笔记

【强化学习】gymnasium自定义环境并封装学习笔记 gym与gymnasium简介gymgymnasium gymnasium的基本使用方法使用gymnasium封装自定义环境官方示例及代码编写环境文件__init__()方法reset()方法step()方法render()方法close()方法 注册环境创建包 Package&#xff08;最后一步&a…

【QT5】<知识点> QT常用知识(更新中)

目录 一、更改文本颜色和格式 二、QT容器类 三、字符串与整数、浮点数之间的转换 四、QString常用功能 五、SpinBox的属性介绍 六、滑动、滚动、进度条和表盘LCD 七、时间、日期、定时器 一、更改文本颜色和格式 动态设置字体粗体&#xff1a;QFont对象的setBold方法动态…

Yapi代码执行 waf绕过实战记录

本文记录了2021年一次有趣的客户目标测试实战。这次经历颇为特别&#xff0c;因此我将其整理成笔记&#xff0c;并在此分享&#xff0c;希望对大家有所帮助。 事件起因 疫情在家办公&#xff0c;准备开始划水的一天&#xff0c;这时接到 boss 的电话说要做项目&#xff0c;老…

微调技术:人工智能领域的神奇钥匙

在人工智能的浪潮中&#xff0c;深度学习技术凭借其强大的数据处理和学习能力&#xff0c;已成为推动科技进步的重要引擎。然而&#xff0c;深度学习模型的训练往往需要大量的数据和计算资源&#xff0c;这在某些特定场景下成为了限制其发展的瓶颈。为了解决这个问题&#xff0…

SolidWorks 2016 SP5安装教程

软件介绍 Solidworks软件功能强大&#xff0c;组件繁多。 Solidworks有功能强大、易学易用和技术创新三大特点&#xff0c;这使得SolidWorks 成为领先的、主流的三维CAD解决方案。 SolidWorks 能够提供不同的设计方案、减少设计过程中的错误以及提高产品质量。SolidWorks 不仅…

JavaWeb6 Tomcat+postman请求、响应

Web服务器 对HTTP协议操作进行封装&#xff0c;简化web程序开发 部署web项目&#xff0c;对外提供网上信息浏览服务 Tomcat 轻量级web服务器&#xff0c;支持servlet&#xff0c;jsp等少量javaEE规范 也被称为web容器&#xff0c;servlet容器 Springboot有内置Tomcat nginx…

网络编程2----UDP简单客户端服务器的实现

首先我们要知道传输层提供的协议主要有两种&#xff0c;TCP协议和UDP协议&#xff0c;先来介绍一下它们的区别&#xff1a; 1、TCP是面向连接的&#xff0c;UDP是无连接的。 连接的本质是双方分别保存了对方的关键信息&#xff0c;而面向连接并不意味着数据一定能正常传输到对…