移动端双验证码登录实现

news/2025/2/11 4:11:15/

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

分析

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

在这里插入图片描述

说明:

  • (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="javascript language-javascript">getImg()"><br>输入短信验证码:<input type="text" id="msg"><input type="button" value="获取短信验证码" onclick="javascript language-javascript">getMsg()"><br><p></p><input type="button" value="登录" onclick="javascript language-javascript">login()">
</body>
<script>javascript">// 图形验证码返回的uuidlet uuid = "";// 短信验证码返回的msgIdlet msgId = "";function getImg() {// 异步交互ajaxaxios.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类

java">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 {@Beanpublic 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;}
}

三个接口实现;

java">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 {@ResourceDefaultKaptcha defaultKaptcha;@Autowiredprivate 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存入redisString 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存入redisString 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.ppmy.cn/news/1427578.html

相关文章

Since Maven 3.8.1 http repositories are blocked.

编译maven 项目时候报错提示下面信息&#xff1a; Since Maven 3.8.1 http repositories are blocked.Possible solutions: - Check that Maven settings.xml does not contain http repositories - Check that Maven pom files do not contain http repository http://XXXXXX:…

信号分解 | VMD(变分模态分解)-Matlab

分解效果 VMD(变分模态分解) 变分模态分解(Variational Mode Decomposition,VMD)是一种信号分解方法,用于将非平稳信号分解为一组模态函数。VMD是一种自适应的数据驱动方法,可以有效地处理具有非线性和非平稳特性的信号。 VMD的基本思想是通过迭代优化过程,将原始信号分…

一起Talk Android吧(第五百五十七回:如何获取文件读写权限)

文章目录 1. 概念介绍2. 使用方法3. 示例代码4. 内容总结各位看官们大家好,上一回中分享了一个Retrofit使用错误的案例,本章回中将介绍 如何获取文件读写权限。闲话休提,言归正转,让我们一起Talk Android吧! 1. 概念介绍 我们在本章回中说的文本读写权限是指读写手机中的…

Android开发——Fragment

Demo fragment_blank.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_pare…

【面试经典 150 | 二叉搜索树】验证二叉搜索树

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;中序遍历方法二&#xff1a;递归 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及…

使用Unity 接入 Stable-Diffusion-WebUI的 文生图api 并生成图像

使用Unity 接入 Stable-Diffusion-WebUI 文生图生成图像 文章目录 使用Unity 接入 Stable-Diffusion-WebUI 文生图生成图像一、前言二、具体步骤1、启动SD的api设置2、unity 创建生图脚本3、Unity 生图交互配置步骤 1: 创建sdControl步骤2&#xff1a;生成后图片画布步骤3&…

【树莓派学习】hello,world!

系统安装及环境配置详见【树莓派学习】系统烧录及VNC连接、文件传输-CSDN博客 树莓派内置python3&#xff0c;可以直接利用python输出。

solidity入门

Solidity 是以太坊智能合约开发的主要编程语言&#xff0c;支持多种数据类型&#xff0c;其中数组是一种非常常用和灵活的数据结构。在本教程中&#xff0c;我们将深入探讨 Solidity 中数组的各种类型、创建规则以及常见操作。 ### 固定长度数组 固定长度数组在声明时指定了数…