基于 Vue 和 Spring Boot 实现滑块验证码的机器验证

基于 Vue 和 Spring Boot 实现滑块验证码的机器验证

  • 需求概述
  • 技术选型
  • 前端实现
    • 1. 引入组件
    • 2. 修改后端请求URL
    • 3. 新增机器验证页面
    • 4.首页调用验证组件
  • 后端实现
    • 流程梳理
    • 具体实现
      • 1. 引入依赖
      • 2. 增加yml配置
      • 3. 代码实现
      • 4.跨域配置(可选)
  • 实现效果
  • 二次验证的实现
      • 为什么需要二次验证?
    • 前端实现
      • 1.APP.vue
      • 2.新增登录和验证页面
      • 3.配置路由
    • 后端实现
    • 访问与实现效果
  • 其他问题整合
    • 移动端滑动与浏览器默认左滑返回上一页冲突
  • Demo地址

本文详细介绍了如何使用 AJ-Captcha 库实现一个安全的移动端登录系统,包含滑块验证码(行为验证)功能。用户需要输入手机号,完成滑块验证,并由后端校验手机号是否存在后才能触发短信验证码发送。本实现参考了 Gitee 的手机号登录流程。

实现效果如下:

录制_2025_06_05_13_57_46_727

需求概述

登录流程如下:

  1. 用户输入手机号,前端进行格式校验。
  2. 点击“发送验证码”后触发滑块验证码。
  3. 滑块验证通过后,后端校验手机号是否存在。
  4. 手机号存在则发送短信验证码,用户输入验证码后完成登录。

以下是参考的 Gitee 登录流程时序图和界面示意图:

image-20250603100925555

机器验证登录时序图

技术选型

  • 采用 AJ-Captcha 行为验证码库,支持滑动拼图和文字点选两种验证方式。AJ-Captcha 提供前后端交互的完整解决方案,支持 Java 后端以及多种前端框架(Vue、React、Flutter 等)。

  • 功能特点

    • 嵌入式集成,接入简单,安全高效。

    • 抛弃传统字符验证码,优化用户体验。

    • 支持多种前端框架和后端缓存方式(Redis、内存等)。

    • 提供滑动拼图和文字点选两种验证码类型。

    • 在线文档:AJ-Captcha 官方文档

前端实现

参考文档:vue | AJ-Captcha

1. 引入组件

view/vue3/src/components/verifition · anji-plus/AJ-Captcha - 码云 - 开源中国

# 1.复制view/vue/src/components/verifition文件夹,到自己工程对应目录下,在登录页面插入如下代码。
# 2.安装请求和加密依赖
npm install axios  crypto-js   -S

2. 修改后端请求URL

修改aixos.js中的axios.defaults.baseURL为后端服务的url

image-20250605134920919

3. 新增机器验证页面

image-20250605134736543

<!-- src/views/TestVerification.vue -->
<template><Verify@success="success":mode="'pop'":captchaType="'blockPuzzle'":imgSize="{ width: '330px', height: '155px' }"ref="verify"/><button @click="useVerify">调用验证组件</button>
</template><script>
import Verify from '../components/verifition/Verify.vue'export default {name: 'TestVerification',components: { Verify },methods: {success(params) {console.log('验证成功,返回参数:', params)},useVerify() {this.$refs.verify.show()}}
}
</script>

4.首页调用验证组件

修改App.vue文件

<template><verify/>
</template><script>
import verify from './views/TestVerification.vue'export default {name: 'App',components: {verify}
}
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

后端实现

流程梳理

  1. 用户输入手机号 -> 前端格式校验 -> 点击“发送验证码”

  2. 调用后端机器校验初始化接口 -> 返回滑块校验参数 -> 跳转滑块页面

  3. 前端滑块完成 -> 调用后端滑块校验接口 + 传滑块验证数据 + 手机号

  4. 后端验证滑块数据:

    • 成功 -> 校验手机号是否存在
      • 存在 -> 返回允许发送验证码
      • 不存在 -> 返回错误提示手机号不存在
    • 失败 -> 返回滑块验证失败
  5. 前端收到允许发送验证码后 -> 调用发送验证码接口(携带手机号)

  6. 用户输入验证码 -> 调用登录接口(手机号+验证码)

具体实现

1. 引入依赖

<dependency><groupId>com.anji-plus</groupId><artifactId>captcha-spring-boot-starter</artifactId><version>1.4.0</version>
</dependency>

复制 images文件夹到resources目录下,项目地址:images · anji-plus/AJ-Captcha - 码云 - 开源中国

image-20250605105758345

image-20250605105910474

2. 增加yml配置

aj:captcha:jigsaw: classpath:images/jigsaw#滑动验证,底图路径,不配置将使用默认图片##支持全路径# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-clickpic-click: classpath:images/pic-click# 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis或者memcache,# 参考CaptchaCacheServiceRedisImpl.java# 如果应用是单点的,也没有使用redis,那默认使用内存。# 内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息不同步,导致失败。# !!! 注意啦,如果应用有使用spring-boot-starter-data-redis,# 请打开CaptchaCacheServiceRedisImpl.java注释。# redis ----->  SPI: 在resources目录新建META-INF.services文件夹(两层),参考当前服务resources。# 缓存local/redis...cache-type: local# local缓存的阈值,达到这个值,清除缓存cache-number: 1000# local定时清除过期缓存(单位秒),设置为0代表不执行timing-clear: 180# 验证码类型default两种都实例化。type: default# 汉字统一使用Unicode,保证程序通过@value读取到是中文,可通过这个在线转换;yml格式不需要转换# https://tool.chinaz.com/tools/unicode.aspx 中文转Unicode# 右下角水印文字(我的水印)water-mark: water-mark# 右下角水印字体(不配置时,默认使用文泉驿正黑)# 由于宋体等涉及到版权,我们jar中内置了开源字体【文泉驿正黑】# 方式一:直接配置OS层的现有的字体名称,比如:宋体# 方式二:自定义特定字体,请将字体放到工程resources下fonts文件夹,支持ttf\ttc\otf字体# aj.captcha.water-font=WenQuanZhengHei.ttf# water-font: SourceHanSansCN-Normal.otf# 点选文字验证码的文字字体(文泉驿正黑)# aj.captcha.font-type=WenQuanZhengHei.ttf# font-type: SourceHanSansCN-Normal.otf# 校验滑动拼图允许误差偏移量(默认5像素)slip-offset: 5# aes加密坐标开启或者禁用(true|false)aes-status: true# 滑动干扰项(0/1/2)interference-options: 1history-data-clear-enable: true# 接口请求次数一分钟限制是否开启 true|falsereq-frequency-limit-enable: true# 验证失败5次,get接口锁定req-get-lock-limit: 5# 验证失败后,锁定时间间隔,sreq-get-lock-seconds: 60# get接口一分钟内请求数限制req-get-minute-limit: 30# check接口一分钟内请求数限制req-check-minute-limit: 60# verify接口一分钟内请求数限制req-verify-minute-limit: 60

3. 代码实现

新增CaptchaController.java类

package com.zhou.demo.controller;import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** 验证码操作处理** @author cxstar*/
@RestController
public class CaptchaController {@Resourceprivate CaptchaService captchaService;/*** 获取验证码接口** @param captchaVO 验证码参数*                  "captchaType": "blockPuzzle",*                  "clientUid": "唯一标识"*/@PostMapping("/captcha/get")public ResponseModel get(@RequestBody CaptchaVO captchaVO) {return captchaService.get(captchaVO);}/*** 校验滑动验证** @param captchaVO 验证码参数*                  "captchaType": "blockPuzzle",*                  "pointJson": "QxIVdlJoWUi04iM+65hTow==",  //aes加密坐标信息*                  "token": "71dd26999e314f9abb0c635336976635"  //get请求返回的token*/@PostMapping("/captcha/check")public ResponseModel check(@RequestBody CaptchaVO captchaVO) {return captchaService.check(captchaVO);}}

4.跨域配置(可选)

如果报错如下:

Access to XMLHttpRequest at 'http://localhost:9001/captcha/get' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

在后端增加过滤配置:

image-20250605114805579

package com.zhou.demo;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 匹配所有接口.allowedOriginPatterns("*") // 允许所有来源(Spring Boot 2.4+ 推荐用 allowedOriginPatterns).allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true) // 允许携带 Cookie.maxAge(3600); // 预检请求缓存时间,单位秒}
}

实现效果

录制_2025_06_05_13_57_46_727

如上所述已经实现了基础的验证功能

二次验证的实现

为什么需要二次验证?

若不进行后端二次验证,攻击者可直接构造验证码发送请求,绕过滑块验证,导致短信接口被恶意刷取。二次验证通过后端校验滑块结果,确保请求合法性。

前端实现

以下内容基于上述基础实现

1.APP.vue

主要去除了原先的测试组件

<template><router-view/>
</template><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}nav {padding: 30px;
}nav a {font-weight: bold;color: #2c3e50;
}nav a.router-link-exact-active {color: #42b983;
}
</style>

2.新增登录和验证页面

LoginPage.vue 登录页面

<template><div class="login-container"><h2>登录</h2><div class="input-group"><input type="tel" v-model="phoneNumber" placeholder="请输入手机号" @input="validatePhone" /><span v-if="phoneError" class="error-message">{{ phoneError }}</span></div><div class="captcha-group"><input type="text" v-model="captcha" placeholder="请输入验证码" /><button @click="goToSlider" class="captcha-btn" :disabled="!!phoneError">获取验证码</button></div><button @click="login" class="login-btn">登录</button></div>
</template><script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';const phoneNumber = ref('');
const captcha = ref('');
const phoneError = ref('');
const router = useRouter();const validatePhone = () => {const phoneRegex = /^\d{11}$/;if (!phoneNumber.value) {phoneError.value = '手机号不能为空';} else if (!phoneRegex.test(phoneNumber.value)) {phoneError.value = '请输入有效的11位手机号';} else {phoneError.value = '';}
};const goToSlider = () => {if (!phoneError.value) {console.log('跳转到滑块验证页面:', phoneNumber.value)router.push({ path: '/slider', query: { phone: phoneNumber.value } });} else {console.log('手机号格式错误,未跳转:', phoneError.value);}
};const login = () => {if (!phoneNumber.value || !captcha.value || phoneError.value) {alert('请填写正确的手机号和验证码!');return;}console.log('登录中...', {phoneNumber: phoneNumber.value,captcha: captcha.value});
};
</script><style scoped>
/* 保持原有样式不变 */
.login-container {width: 300px;margin: 100px auto;padding: 20px;border: 1px solid #ccc;border-radius: 5px;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);text-align: center;
}h2 {color: #333;margin-bottom: 20px;
}.input-group {margin-bottom: 15px;position: relative;
}input {width: 100%;padding: 10px;border: 1px solid #ccc;border-radius: 5px;font-size: 14px;
}.captcha-group {display: flex;align-items: center;margin-bottom: 15px;
}.captcha-group input {flex: 1;margin-right: 10px;
}.captcha-btn {padding: 10px 20px;background-color: #ff8c00;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 14px;
}.captcha-btn:hover {background-color: #e07b00;
}.captcha-btn:disabled {background-color: #ccc;cursor: not-allowed;
}.login-btn {width: 100%;padding: 10px;background-color: #ff8c00;color: white;border: none;border-radius: 5px;font-size: 16px;cursor: pointer;
}.login-btn:hover {background-color: #e07b00;
}.error-message {color: #ff0000;font-size: 12px;position: absolute;top: 100%;left: 0;margin-top: 5px;
}
</style>

SliderVerification.vue 验证页面

<template><div><div class="phone-input"><inputv-model="phone"type="text"placeholder="请输入手机号码"@input="validatePhone"/><span v-if="phoneError" class="error">{{ phoneError }}</span></div><Verify@success="success":mode="'pop'":captchaType="'blockPuzzle'":imgSize="{ width: '330px', height: '155px' }"ref="verify"/></div>
</template><script>
import Verify from "./../components/verifition/Verify";
import request from "@/components/verifition/utils/axios";export default {name: "App",components: {Verify,},data() {return {phone: "",phoneError: "",};},mounted() {// 显示验证弹窗this.$refs.verify.show();},methods: {validatePhone() {// 基本的手机号码验证(示例:10-12位数字)const phoneRegex = /^\d{10,12}$/;this.phoneError = phoneRegex.test(this.phone)? "": "请输入有效的手机号码(10-12位数字)";},success(params) {// 构建 SmsRequest 数据const smsRequest = {// phone: this.phone, // 来自用户输入,这里只做二次验证的演示,因此不传入phone参数captchaVerification: params.captchaVerification, // 来自 Verify 组件};debugger// 发送请求到后端request({url: "/sendLoginCode",method: "post",data: smsRequest,}).then((response) => {console.log("短信请求成功:", response.data);// 跳转到登录页面this.$router.push("/login");}).catch((error) => {console.error("短信请求失败:", error);alert("发送验证码失败,请重试。");});},},
};
</script><style scoped>
.phone-input {margin-bottom: 20px;
}input {padding: 10px;width: 200px;border: 1px solid #ccc;border-radius: 4px;
}.error {color: red;font-size: 12px;margin-top: 5px;display: block;
}
</style>

3.配置路由

src/router/index.js

import { createRouter, createWebHistory } from 'vue-router';
import LoginPage from '../views/LoginPage.vue';
import SliderVerification from '../views/SliderVerification.vue';const routes = [{ path: '/login', component: LoginPage },{path: '/slider',component: SliderVerification,props: (route) => ({ phone: route.query.phone })},{ path: '/', redirect: '/login' }
];const router = createRouter({history: createWebHistory(),routes
});export default router;

后端实现

新增发送验证码接口

/*** 发送验证码接口,需要二次验证** @param captchaVO* @return*/
@RequestMapping(value = "/sendLoginCode", method = RequestMethod.POST)
public ResponseModel sendRegisterCode(@RequestBody CaptchaVO captchaVO) {// 判断是否已经通过滑块验证ResponseModel response = captchaService.verification(captchaVO);System.out.println(response.isSuccess() ? "二次验证通过" : "二次验证未通过");// todo 发送验证码return response;
}

访问与实现效果

访问地址:http://localhost:8081/login

录制_2025_06_05_17_20_30_669

其他问题整合

移动端滑动与浏览器默认左滑返回上一页冲突

解决方案:限制滑动区域不贴边

避免滑块靠近屏幕左侧边缘(<20px),可在布局上做调整,例如:

.slider-container {margin-left: 20px; /* 留出边缘,避免触发左滑返回 */max-width: 90%;
}

参考链接:阻止移动端H5开发浏览器默认左右滑动行为_h5 touch 与 浏览器左右滑冲突-CSDN博客

Demo地址

https://zhouquanquan.lanzn.com/iTLVG2y754ng
密码:hb4d

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

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

相关文章

[Java恶补day13] 53. 最大子数组和

休息了一天&#xff0c;开始补上&#xff01; 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums …

sql server如何创建表导入excel的数据

在 SQL Server 中&#xff0c;可以通过几种方式将 Excel 数据导入到数据库表中。下面是一个完整的流程&#xff0c;包括如何创建表&#xff0c;以及将 Excel 数据导入该表的方法&#xff1a; ✅ 方法一&#xff1a;使用 SQL Server Management Studio (SSMS) 的导入向导&#x…

C++单例模式教学指南

C单例模式完整教学指南 &#x1f4da; 目录 [单例模式基础概念][经典单例实现及问题][现代C推荐实现][高级话题&#xff1a;双重检查锁][实战应用与最佳实践][总结与选择指南] 1. 单例模式基础概念 1.1 什么是单例模式&#xff1f; 单例模式&#xff08;Singleton Pattern&…

使用xdocreport导出word

之前java总用freemaker进行导出&#xff0c;但是改xml实在是太繁琐了&#xff0c;这次找了另一个工具进行体验. 一、简单导出 pom引入 <dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.core</arti…

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …

C++.OpenGL (2/64)你好,三角形(Hello Triangle)

你好,三角形(Hello Triangle) 绘制流程概览 #mermaid-svg-MvIGIovxiuKVfzy8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MvIGIovxiuKVfzy8 .error-icon{fill:#552222;}#mermaid-svg-MvIGIovxiuKVfzy8 .error…

汽车安全体系:FuSa、SOTIF、Cybersecurity 从理论到实战

汽车安全&#xff1a;功能安全&#xff08;FuSa&#xff09;、预期功能安全&#xff08;SOTIF&#xff09;与网络安全(Cybersecurity) 从理论到实战的安全体系 引言&#xff1a;自动驾驶浪潮下的安全挑战 随着自动驾驶技术从L2向L4快速演进&#xff0c;汽车安全正从“机械可靠…

N2语法 列挙、話題提出

1&#xff0c;&#xff5e;やら&#xff5e;やら  接続&#xff1a;名詞、辞書形  意味&#xff1a;…啦…啦&#xff08;列举代表性的事物&#xff09;  例文&#xff1a;     家に帰って料理やら洗濯やら何もしなければならない。     帰国前、買い物やら荷造りや…

深入理解React Hooks的原理与实践

深入理解React Hooks的原理与实践 引言 React Hooks 自 2018 年 React 16.8 发布以来&#xff0c;彻底改变了前端开发者的编码方式。它通过函数式组件提供了状态管理和生命周期等功能&#xff0c;取代了传统的类组件&#xff0c;使得代码更加简洁、复用性更强。然而&#xff…

RockyLinux9.6搭建k8s集群

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

链游技术破壁:NFT资产确权与Play-to-Earn经济模型实战

链游技术破壁&#xff1a;NFT资产确权与Play-to-Earn经济模型实战 ——从「投机泡沫」到「可持续生态」的技术重构 一、NFT确权技术革新&#xff1a;从链上存证到动态赋权 跨链确权架构 全链互操作协议&#xff1a;采用LayerZero协议实现以太坊装备与Solana土地的跨链组合&…

Java下载文件(特殊字符编码处理)

当你在这个问题上花费了数小时而解决不了&#xff0c;你才会知道这篇文章对你的帮助 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; import org.springframewo…

TDengine 高级功能——读缓存

简介 在物联网&#xff08;IoT&#xff09;和工业互联网&#xff08;IIoT&#xff09;大数据应用场景中&#xff0c;实时数据的价值往往远超历史数据。企业不仅需要数据处理系统具备高效的实时写入能力&#xff0c;更需要能快速获取设备的最新状态&#xff0c;或者对最新数据进…

YOLO在C#中的完整训练、验证与部署方案

YOLO在C#中的完整训练、验证与部署方案 C# 在 YOLO 部署上优势明显&#xff08;高性能、易集成&#xff09;&#xff0c;但训练能力较弱&#xff0c;通常需结合 Python 实现。若项目对开发效率要求高且不依赖 C# 生态&#xff0c;建议全程使用 Python&#xff1b;若需深度集成…

pikachu靶场通关笔记17 CSRF关卡03-CSRF(Token)

目录 一、CSRF原理 二、CSRF Token 三、源码分析 四、CSRF Token tracker插件 1、插件简介 2、插件安装 五、渗透实战 1、用户登录 2、修改个人信息 3、bp拦截报文 4、bp改报文探测 5、配置CSRF-Token-Tracer 6、bp改包成功 7、查看CSRF Token Tracker配置 本系…

C#面试问题81-100

85. What are anonymous types? 匿名类型是在需要的地方直接定义的类型&#xff0c;甚至都 不给它命名。它非常适合我们这种用例——类型小且临时&#xff0c;而且我们无意在其 他地方使用它 匿名类型是直接从 System.Object 派生的类对象。它们不能转换为任何 其他类型。●…

【Ragflow】25.Ragflow-plus开发日志:excel文件解析新思路/公式解析适配

引言 RagflowPlus v0.3.0 版本中&#xff0c;增加了对excel文件的解析支持&#xff0c;但收到反馈&#xff0c;说效果并不佳。 以下测试文件内容来自群友反馈提供&#xff0c;数据已脱敏处理。 经系统解析后&#xff0c;分块效果如下&#xff1a; 可以看到&#xff0c;由于该…

VS2022下C++ Boost库安装与使用使用

一.Boost概述 1.简介 Boost 是一个广泛使用的 C 库集合&#xff0c;提供了许多高质量、可移植、高效的工具和组件&#xff0c;被视为 C 标准库的延伸。自 1998 年成立以来&#xff0c;Boost 已成为 C 社区的核心资源&#xff0c;许多 Boost 库通过实践验证后被纳入 C 标准&am…

内嵌式mqtt server

添加moquette依赖 <dependency><groupId>io.moquette</groupId><artifactId>moquette-broker</artifactId><version>0.17</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>…

php执行后报502,无错误提示的排查和解决

文章目录 一、阐述问题二、开始排查1.执行代码展示2.PHP层面排查问题3.系统层面排查问题1. 分析系统日志2. core dump 分析2.1 core dump 是什么2.2 core dump 配置 并 生成 core 文件2.3 gdb 解析 core 文件 4. 问题解决 三、赠送内容四、总结 一、阐述问题 这个问题花了我起…