移动端双验证码登录实现

news2024/11/27 22:31:50

说明:本文介绍如何用图形验证码+短信验证码实现移动端登录思路;

分析

通过手机号+图形验证码+手机验证码实现登录的时序图如下:

在这里插入图片描述

说明:

  • (1)用户进入登录界面,出现图形验证码,可点击图形验证码更换图片;

  • (2)后端返回图形验证码的base64地址,加上一个uuid,该uuid为验证码在Redis中存储的Key;

  • (3)用户输入手机号、uuid、图形验证码,获取手机短信验证码;

  • (4)后端根据uuid去Redis中获取图形验证码,与用户输入的进行比较,通过发送短信验证码,同时将短信验证码的MessageId与验证码存入Redis中,不通过返回错误信息;

  • (5)用户输入手机号、uuid、messageId、图形验证码、手机验证码登录;

  • (6)后端根据uuid、messageId去Redis中获取验证码,分别与用户输入的验证码比较,通过登录成功,发Token,不通过返回错误信息;

前端实现

首先,做一个简单的页面,如下:

在这里插入图片描述

页面有三个接口,分别是:获取图形验证码,获取短信验证码,登录,代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>获取验证码</title>

    <script src="js/axios-0.18.0.js"></script>

</head>

<body>
    输入手机号:<input type="text" id="phone">
    <br>
    输入图形验证码:<input type="text" id="img">
    <img id="pic" />
    <input type="button" value="获取图形验证码" onclick="getImg()">
    <br>
    输入短信验证码:<input type="text" id="msg">
    <input type="button" value="获取短信验证码" onclick="getMsg()">
    <br>
    <p></p>
    <input type="button" value="登录" onclick="login()">
</body>
<script>
    // 图形验证码返回的uuid
    let uuid = "";
    // 短信验证码返回的msgId
    let msgId = "";

    function getImg() {
        // 异步交互ajax
        axios.get("http://localhost:8080/getImg")
            .then(response => {
                // 接收响应回来的数据
                console.log(response.data);
                uuid = response.data.uuid;
                document.getElementById("pic").src = 'data:image/jpeg;base64,' + response.data.data;
            })
    }

    function getMsg() {
        // 手机号
        const phone = document.getElementById("phone").value;
        // 图形验证码
        const imgValue = document.getElementById("img").value;

        const data = {
            phone:phone,
            imgValue: imgValue,
            uuid: uuid
        };
        // 发送 POST 请求
        axios.post("http://localhost:8080/getMsg", data)
            .then(response => {
                console.log("请求发送成功:", response.data);
                msgId = response.data.msgId
            })
            .catch(error => {
                console.error("请求发送失败:", error);
            });
    }

    function login() {
        // 手机号
        const phone = document.getElementById("phone").value;
        // 图形验证码
        const imgValue = document.getElementById("img").value;
        // 短信验证码
        const msgValue = document.getElementById("msg").value;

        // 构造登录请求的数据对象
        const loginData = {
            phone: phone,
            imgValue: imgValue,
            msgValue: msgValue,
            uuid: uuid,
            msgId: msgId
        };

        // 发送 POST 请求
        axios.post("http://localhost:8080/login", loginData)
            .then(response => {
                console.log("登录成功:", response.data);
            })
            .catch(error => {
                console.error("登录失败:", error);
            });
    }
</script>

</html>

图像验证码使用使用Kaptcha实现,参考:

  • 使用Kaptcha生成验证码

后端实现

KaptchConfig类

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.Properties;

@Component
public class KaptchConfig {

    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        // 创建验证码工具
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();

        // 验证码配置
        Properties properties = new Properties();

        // 图片边框
        properties.setProperty("kaptcha.border", "no");

        // 边框颜色
        properties.setProperty("kaptcha.border.color", "black");

        //边框厚度
        properties.setProperty("kaptcha.border.thickness", "1");

        // 图片宽
        properties.setProperty("kaptcha.image.width", "120");

        // 图片高
        properties.setProperty("kaptcha.image.height", "60");

        //图片实现类
        properties.setProperty("kaptcha.producer.impl", "com.google.code.kaptcha.impl.DefaultKaptcha");

        //文本实现类
        properties.setProperty("kaptcha.textproducer.impl", "com.google.code.kaptcha.text.impl.DefaultTextCreator");

        //文本集合,验证码值从此集合中获取
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");

        //验证码长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");

        //字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体");

        //字体颜色
        properties.setProperty("kaptcha.textproducer.font.color", "black");

        //文字间隔
        properties.setProperty("kaptcha.textproducer.char.space", "4");

        //干扰实现类
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");

        //干扰颜色
        properties.setProperty("kaptcha.noise.color", "blue");

        //干扰图片样式
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");

        //背景实现类
        properties.setProperty("kaptcha.background.impl", "com.google.code.kaptcha.impl.DefaultBackground");

        //背景颜色渐变,结束颜色
        properties.setProperty("kaptcha.background.clear.to", "white");

        //文字渲染器
        properties.setProperty("kaptcha.word.impl", "com.google.code.kaptcha.text.impl.DefaultWordRenderer");

        // 创建验证码配置实例
        Config config = new Config(properties);

        // 验证码工具
        defaultKaptcha.setConfig(config);

        return defaultKaptcha;
    }
}

三个接口实现;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
@CrossOrigin
public class KaptchController {

    @Resource
    DefaultKaptcha defaultKaptcha;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 生成图形验证码
     */
    @GetMapping("/getImg")
    public Map getImg() throws IOException {
        // 生成文字验证码
        String imageCode = defaultKaptcha.createText();

        // 生成图片验证码
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        BufferedImage image = defaultKaptcha.createImage(imageCode);
        ImageIO.write(image, "jpg", out);

        // 生成uuid,将uuid作为key,验证码作为value存入redis
        String uuid = java.util.UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(uuid, imageCode, 60, TimeUnit.SECONDS);

        // 对字节组Base64编码
        return Map.of("data", Base64.getEncoder().encodeToString(out.toByteArray()), "uuid", uuid, "imageCode", imageCode);
    }

    /**
     * 生成短息验证码
     */
    @PostMapping("/getMsg")
    public Map getCode(@RequestBody Map<String, String> map) throws IOException {
        // 获取相关参数
        String phone = map.get("phone");
        String uuid = map.get("uuid");
        String imgValue = map.get("imgValue");

        // 根据uuid获取图形验证码
        String imageCode = redisTemplate.opsForValue().get(uuid);

        // 校验手机号是否合法
        if (phone == null || phone.length() != 11) {
            return Map.of("data", "手机号不合法");
        }

        // 图形验证码是否过期
        if (imageCode == null) {
            return Map.of("data", "验证码已过期");
        }

        // 是否输入正确
        if (!imageCode.toUpperCase().equals(imgValue.toUpperCase())) {
            return Map.of("data", "验证码错误");
        }

        // 生成6位数的短信验证码
        String msgCode = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));

        // 生成msgId,将msgId作为key,验证码作为value存入redis
        String msgId = java.util.UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(msgId, msgCode, 60, TimeUnit.SECONDS);

        return Map.of("data", msgCode, "msgId", msgId);
    }

    /**
     * 登录
     */
    @PostMapping("/login")
    public Map login(@RequestBody Map<String, String> map) {
        // 获取相关参数
        String phone = map.get("phone");
        String uuid = map.get("uuid");
        String imgValue = map.get("imgValue");
        String msgId = map.get("msgId");
        String msgValue = map.get("msgValue");

        // 根据uuid、msgId获取图形验证码、短信验证码
        String imageCode = redisTemplate.opsForValue().get(uuid);
        String msgCode = redisTemplate.opsForValue().get(msgId);

        // 校验手机号是否合法
        if (phone == null || phone.length() != 11) {
            return Map.of("data", "手机号不合法");
        }

        // 图形验证码是否过期
        if (imageCode == null) {
            return Map.of("data", "验证码已过期");
        }

        // 是否输入正确
        if (!imageCode.toUpperCase().equals(imgValue.toUpperCase())) {
            return Map.of("data", "验证码错误");
        }

        // 短信验证码是否过期
        if (msgCode == null) {
            return Map.of("data", "验证码已过期");
        }

        // 是否输入正确
        if (!msgCode.equals(msgValue)) {
            return Map.of("data", "验证码错误");
        }

        // 登录成功,删除Redis中的验证码
        redisTemplate.delete(uuid);
        redisTemplate.delete(msgId);

        return Map.of("data", "success");
    }
}

测试

测试正常情况

在这里插入图片描述

测试图形验证码输入错误的情况

在这里插入图片描述

测试短信验证码输入错误的情况

在这里插入图片描述

基本实现了,正式情况还需要考虑更严格的手机号校验,手机验证码防频繁点击,手机短信登录第三方API接入,规范验证码在Redis中Key的格式,验证码在Redis中的过期时间等等,这里仅是一个Demo,但上述实现思路是值得考虑的。

总结

本文介绍了移动端双验证码登录的实现,希望能对大家有所启发。

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

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

相关文章

Jsp 中的getServletContext全局数据共享

servletContext作用于不同用户之上 1. 一个用户将数据保存到了servletContext中, // getcontext的servlet程序 Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context this.get…

upload-labs靶场详解

靶场环境 下载链接&#xff1a;https://codeload.github.com/c0ny1/upload-labs/zip/refs/heads/master 使用小皮集成环境来完成这个靶场 将文件放到WWW目录下就可以进行访问 进入关卡后页面呈现&#xff1a; Pass-01&#xff08;前端绕过&#xff09; 我们先尝试上传一个web.…

如何通过Postgres的日志进行故障排查?

文章目录 一、配置日志记录二、查看和分析日志三、使用日志进行故障排查的示例四、总结 在进行数据库管理和维护时&#xff0c;日志分析是一项至关重要的技能。PostgreSQL的日志记录功能可以帮助我们追踪数据库的运行状态&#xff0c;定位问题&#xff0c;以及优化性能。下面&a…

7-6 铺满方格

有一个1n的长方形,由边长为1的n个方格构成,例如,当n=3时为13的方格长方形如下图所示。求用11、12、13的骨牌铺满方格的方案总数。 输入格式: 测试数据有多组,处理到文件尾。每组测试输入一个整数n(1≤n≤50)。 输出格式: 对于每组测试,输出一行,包含一个整数,表示用…

科学计算与人工智能

人工智能的“科技幻觉” “美丽、白雪皑皑的东京市熙熙攘攘。镜头追随着人们&#xff0c;一同欣赏美丽的雪景和热闹的摊位&#xff0c;感受雪花纷飞&#xff0c;樱花起舞。”&#xff08;翻译自英文Prompt&#xff09; 这如同现实场景的画面&#xff0c;并非出自摄影师的镜头&…

链表OJ - 6(链表分割)

题目描述&#xff08;来源&#xff09; 现有一链表的头指针 ListNode* pHead&#xff0c;给一定值x&#xff0c;编写一段代码将所有小于x的结点排在其余结点之前&#xff0c;且不能改变原来的数据顺序&#xff0c;返回重新排列后的链表的头指针。 思路 创建两个链表&#xff0c…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《含状态耦合约束的分布式船舶储能系统两层能量管理方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

24年云南三支一扶报名时间线报名流程

一、报名阶段 1、阅读公告&#xff1a;4月17日起 2、提交报考申请&#xff1a;4月22日09:00至4月25日18:00 3、审核报名资格&#xff1a;4月22日09:00至4月26日18:00 4、公告有效招募岗位&#xff1a;4月28日 5、所报岗位被取消改报岗位&#xff1a;4月29日09:00至16:00 6、打印…

MySql 表中的id突然变很大,如何给id重新排序

目录 一、场景 二、解决方法 一、场景 我们在开发过程中&#xff0c;难免遇到id突然增大的情况。 由于id突然增大很多&#xff0c;我们重新增加数据时候id会默认加1 那么如何让id 重新从1按顺序排序呢 二、解决方法 点击编辑表&#xff0c;然后新建一个字段id2&#xff0c;将…

使用lambda表达式Collectors.toMap 遇到的报错,带有源码分析

概述 正常hashMap中的key和value都允许为null&#xff0c;但是在list转map中&#xff0c;使用lambda表达式要求key和value都不能为null。这很反常识 起因 本身上游返回contentId和traceId 内容id和跟踪id&#xff0c;但是项目人员变动修改了接口没有给traceId导致 代码 pu…

SpringBoot集成RockerMQ

1.引入依赖 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.0</version> </dependency>2.配置服务器地址 #Rocketmq配置 rocketmq.name-server192…

最新免费 ChatGPT、GPTs、AI换脸(Suno-AI音乐生成大模型)

&#x1f525;博客主页&#xff1a;只恨天高 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ ChatGPT3.5、GPT4.0、GPTs、AI绘画相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容…

【配电网故障定位】基于二进制混合灰狼粒子群算法的配电网故障定位 33节点配电系统故障定位【Matlab代码#79】

文章目录 【获取资源请见文章第6节&#xff1a;资源获取】1. 配电网故障定位2. 二进制混合灰狼粒子群算法3. 算例展示4. 部分代码展示5. 仿真结果展示6. 资源获取 【获取资源请见文章第6节&#xff1a;资源获取】 1. 配电网故障定位 配电系统故障定位&#xff0c;即在配电网络…

安装GPT 学术优化 (GPT Academic)@FreeBSD

GPT 学术优化 (GPT Academic)是一个非常棒的项目 可以帮助我们完成中科院的一些日常工作。 官网&#xff1a;GitHub - binary-husky/gpt_academic: 为GPT/GLM等LLM大语言模型提供实用化交互接口&#xff0c;特别优化论文阅读/润色/写作体验&#xff0c;模块化设计&#xff0c;…

JavaScript(JS)三种使用方式,三种输出方式,及快速注释。---[用于后续web渗透内容]

JavaScript&#xff08;JS&#xff09;是一种广泛使用的编程语言&#xff0c;允许在网页中添加交互性和动态效果。在HTML中&#xff0c;<script>标签用于引入和执行JavaScript代码。 JS代码 js1.html \\js三种使用方式<!DOCTYPE html> <html lang"en&quo…

Tomcat命令行窗口、IDEA中Tomcat控制台 中文乱码问题解决方案

Tomcat出现中文乱码问题 打开Tomcat文件夹下的conf/logging.properties文件&#xff0c;将下图位置中的编码由UTF-8全部替换成GBK 然后重启Tomcat服务器&#xff0c;问题解决 Intellij IDEA启动Tomcat服务器控制台出现中文乱码 解决方案非常简单&#xff0c;按照下图设置控制…

将数字状态码在后台转换为中文状态

这是我们的实体类 可以看出我们的状态status是2如过返回到前端我们根本不知道2代表的是什么&#xff0c;所以我们需要再这里将数字转换成能看懂的中文状态&#xff0c;首先我们创建一个枚举类 先将我们状态码所对应的中文状态枚举出来&#xff0c;然后创建一个静态方法&#…

设计模式学习(六)——《大话设计模式》

设计模式学习&#xff08;六&#xff09;——《大话设计模式》 简单工厂模式&#xff08;Simple Factory Pattern&#xff09;&#xff0c;也称为静态工厂方法模式&#xff0c;它属于类创建型模式。 在简单工厂模式中&#xff0c;可以根据参数的不同返回不同类的实例。简单工厂…

超平实版Pytorch CNN Conv2d

torch.nn.Conv2d 基本参数 in_channels (int) 输入的通道数量。比如一个2D的图片&#xff0c;由R、G、B三个通道的2D数据叠加。 out_channels (int) 输出的通道数量。 kernel_size (int or tuple) kernel&#xff08;也就是卷积核&#xff0c;也可…

基于Springboot的社区防疫物资申报系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区防疫物资申报系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…