移动端双验证码登录实现

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

分析

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

在这里插入图片描述

说明:

  • (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,但上述实现思路是值得考虑的。

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

外贸人寻找客户的6大锦囊 | 进出口的贸易数据服务 | 箱讯科技

一信息特征法---培养一双善于甄别的眼 1、客户的询盘&#xff0c;每个客户在写询盘时用的语言是不一样的&#xff0c;这就构成了客户语言的特征。有的朋友可能发现有的客户英语差的太狠&#xff0c;写出来的询盘很简单很搞笑。如果你一笑而过&#xff0c;那么就太可惜了。这个…

活动的生命周期

返回栈 Android是使用任务(Task)来管理活动的&#xff0c;一个任务就是一组存放在栈里的活动的集合&#xff0c;这个栈也被称作返回栈(Back Stack )。系统总是会显示处于栈顶的活动给用户 活动状态 运行状态当一个活动位于返回栈的栈顶时&#xff0c;这时活动就处于运行状态…

音乐小程序|基于微信开发音乐小程序的系统设计与实现(源码+数据库+文档)

音乐小程序目录 基于微信开发音乐小程序的系统 一、前言 二、系统设计 三、系统功能设计 小程序端&#xff1a; 后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师…

广西建筑模板批发供应,工厂直销

随着广西地区基础设施建设的不断加速,建筑模板作为工程施工的重要辅材,其需求也在持续攀升。在众多建筑模板生产企业中,贵港市能强优品木业有限公司以其25年的丰富生产经验和卓越的产品品质,脱颖而出,成为了广西知名的建筑模板供应商。 能强优品木业公司专注于建筑模板的生产与…

mac IDEA激活 亲测有效

1、官网下载mac版本IDEA并安装 2、打开激活页面 3、下载脚本文件 链接: https://pan.baidu.com/s/1I2BqdfxSJv1A96422rflnA?pwdm494 提取码: m494 4、命令行到该界面&#xff0c;执行 sudo bash idea.sh 可能出现的问题&#xff1a; 查看sh文件&#xff0c;targetFilePath…

西瓜书学习——第一、二章笔记

[] 什么是机器学习? 研究关于“学习算法”(一类能从数据中学习出其背后潜在规律的算法)的一门学科。 PS:深度学习指的是神经网络那一类学习算法&#xff0c;因此是机器学习的子集。 假设空间和版本空间 举个栗子:假设现已收集到某地区近几年的房价和学校数量数据&#xf…

[Java EE] 多线程(二): 线程的创建与常用方法(下)

2.3 启动一个线程–>start() 之前我们已经看到了如何通过重写run()方法来创建一个线程对象,但是线程对象被创建出来并不意味着线程就开始运行了. 覆写run方法是给线程提供了所要做的事情的指令清单创建线程对象就是把干活的人叫了过来.而调用start方法,就是喊一声"行…

国产主流数据库存储类型简析

国产数据库在技术架构上主要分为集中式、基于中间件分布式和原生分布式架构&#xff0c;衍生出集中式架构和分布式架构。那么在这些部署架构中&#xff0c;从数据分布的视角来看&#xff0c;在数据库中数据分布的形态是怎样的。本文将简要分析OceanBase、PolarDB、OpenGauss、G…

【Spring】-编程式事务和声明式事务

spring中控制事务的方式有两种&#xff1a;编程式事务和声明式事务&#xff0c;今天我以两种事务出发&#xff0c;对spring中实现事务的EnableTransactionManagement和Transaction两个注解的底层原理进行讨论。 一、编程式事务 什么是编程式事务&#xff1f; 硬编码的方式实现…

牛客NC197 跳跃游戏(一)【中等 动态规划 Java、Go、PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/23407eccb76447038d7c0f568370c1bd 思路 答案说的merge区间就是每个A[i]的地方能跳到的最远坐标是A[i] [i]&#xff0c; 有一个maxReach&#xff0c;遍历一遍A[i], 不断刷新MaxReach, 如果某个i 位置比maxReac…

MT3023 歌词中找单词

1.暴力 10/12 #include <bits/stdc.h> using namespace std; int n; string a[10005]; int main() {cin >> n;for (int i 0; i < n; i)cin >> a[i];string ll;cin >> ll;for (int i 0; i < n; i){string u a[i];int num 0;int j 0;for (in…

ssm056基于Java语言校园快递代取系统的设计与实现+jsp

校园快递代取系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本校园快递代取系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短…

【在线OJ】雪花算法代码实现

雪花算法 用一个64比特位的long类型来作为生成id的类型&#xff0c;首先我们要了解哪些位置对应的意义&#xff0c;其中在本项目中10位的工作机器id被细分位5bit的机房id与5bit的机器id。雪花算法支持每毫秒生成2的12次方-1个id。 用一个64比特位的long类型来作为生成id的类型…

unity制作拼接地图

前段时间有个朋友问我想要制作一款地图编辑器&#xff0c;最开始我还想着在一个平面用节点切割制作地图编辑器这倒是也行&#xff0c;但不太好控制每一个点&#xff0c;如果未来项目大了&#xff0c;更加不好维护。 偶然间翻到一篇文章&#xff1a;unity地图边缘检测 或许我们…

upload-labs第七八关

第七关 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml"…

SaaS智慧工地云平台源码 支持二次开发、支持源码交付

目录 智慧工地云平台功能模块 一、劳务管理系统 二、视频监控系统 三、危大工程管理 四、环境监测系统 五、材料管理系统 六、进度管理系统 通过人员管理、车辆管理、视频监控、施工质量、设备管理、环境监测、能耗监测七大维度提供面向工程管理人员的现场综合指挥管理平…

51单片机工程模板的建立(基于STC15系列库)

一、开启前准备 1.STC15官方库文件 1.1 stc15-software-lib-v1.0.rar&#xff1b;下载地址&#xff1a;STC15系列库&#xff08;带使用手册&#xff09;资源-CSDN文库 2.Keil4_C51软件&#xff0c;或其它版本&#xff1b; 二、创建工程模板 1.建立文件分类 listing&#xf…

PyTorch深度学习之旅:从入门到精通的十个关键步骤

在人工智能的浪潮中&#xff0c;深度学习框架扮演着至关重要的角色。PyTorch作为其中的佼佼者&#xff0c;以其简洁、直观和灵活的特性&#xff0c;吸引了众多开发者与研究者。本文将引导您逐步掌握PyTorch&#xff0c;从基础概念到高级应用&#xff0c;让您在深度学习的道路上…

斯坦福发布法律指令数据集LawInstruct,统一17个辖区24种语言

在法律领域&#xff0c;语言模型&#xff08;Language Models, LLMs&#xff09;的发展一直面临着独特的挑战。法律文本的复杂性、专业术语的广泛使用以及对精确性和可靠性的极高要求&#xff0c;使得法律领域的自然语言处理&#xff08;Natural Language Processing, NLP&…

工业数学模型——高炉煤气发生量预测(三)

1、工业场景 冶金过程中生产的各种煤气&#xff0c;例如高炉煤气、焦炉煤气、转炉煤气等。作为重要的副产品和二次能源&#xff0c;保证它们的梯级利用和减少放散是煤气能源平衡调控的一项紧迫任务&#xff0c;准确的预测煤气的发生量是实现煤气系统在线最优调控的前提。 2、…