SpringBoot+Vue实现图片滑块和文字点击验证码
一、背景
1.1 概述
传统字符型验证码展示-填写字符-比对答案的流程,目前已可被机器暴力破解,应用程序容易被自动化脚本和机器人攻击。
摒弃传统字符型验证码,采用行为验证码采用嵌入式集成方式,接入方便,安全,高效。验证码展示-采集用户行为-分析用户行为流程,用户只需要产生指定的行为轨迹,不需要键盘手动输入,极大优化了传统验证码用户体验不佳的问题;同时,快速、准确的返回人机判定结果。
1.2 应用场景
- 网站登录:保护用户账号免受非法登录尝试
- 在线表单提交:避免垃圾邮件和恶意数据填充
- 论坛或社区:防止机器人自动发帖和灌水
- 支付验证:保障交易安全,防止欺诈行为
二、anji-plus
AJ-Captcha行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。后端提供Java实现,前端提供了php、angular、html、vue、uni-app、flutter、android、ios等代码示例。
代码开源地址:https://gitee.com/anji-plus/captcha
文档地址:https://ajcaptcha.beliefteam.cn/captcha-doc/
2.1 功能简介
功能 | 描述 |
---|---|
验证码类型 | 滑动拼图 blockPuzzle / 文字点选 clickWord |
验证 | 用户拖动/点击一次验证码拼图即视为一次“验证”,不论拼图/点击是否正确 |
二次校验 | 验证数据随表单提交到后台后,后台需要调用captchaService.verification做二次校验。目的是核实验证数据的有效性。 |
2.2 交互流程
① 用户访问应用页面,请求显示行为验证码
② 用户按照提示要求完成验证码拼图/点击
③ 用户提交表单,前端将第二步的输出一同提交到后台
④ 验证数据随表单提交到后台后,后台需要调用captchaService.verification做二次校验。
⑤ 第4步返回校验通过/失败到产品应用后端,再返回到前端。如下图所示。
三、代码实现
3.1 引入依赖
<dependency><groupId>com.anji-plus</groupId><artifactId>spring-boot-starter-captcha</artifactId><version>1.3.0</version>
</dependency>
3.2 配置
# 验证码配置
aj:captcha:########## 重点关注 ########### 验证码类型:default,blockPuzzle,clickWordtype: default# 底图路径,支持全路径、项目资源路径jigsaw: classpath:images/jigsaw# 滑动图路径,支持全路径、项目资源路径pic-click: classpath:images/pic-click# 缓存类型:local,redis,othercache-type: redis# local缓存的阈值,达到这个值,清除缓存cache-number: 1000# local定时清除过期缓存(单位秒),设置为0代表不执行timing-clear: 180# 滑块验证码的偏移量slip-offset: 5# 滑块验证码的加密坐标aes-status: true# 滑块验证码的滑块干扰项interference-options: 2# 文字验证码的文字数量【暂不可用】click-word-count: 4# 文字验证码的文字字体font-type: WenQuanZhengHei.ttf# 文字验证码的字体样式font-style: 1# 文字验证码的字体大小font-size: 25# 水印文字water-mark: 强哥软件# 水印文字字体water-font: WenQuanZhengHei.ttf########## 接口相关配置 ########### 历史数据清理是否开启history-data-clear-enable: false# 接口请求次数一分钟限制是否开启req-frequency-limit-enable: true# 验证失败5次,get接口锁定req-get-lock-limit: 5# 验证失败后,锁定时间间隔,sreq-get-lock-seconds: 360# get接口一分钟内请求数限制req-get-minute-limit: 30# check接口一分钟内请求数限制req-check-minute-limit: 60# verify接口一分钟内请求数限制req-verify-minute-limit: 60
3.3 自定义验证码存储
使用redis存储验证码
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
实现CaptchaCacheService 接口
package com.qiangesoft.captcha.cache;import com.anji.captcha.service.CaptchaCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.concurrent.TimeUnit;/*** redis缓存验证码** @author qiangesoft* @date 2024-05-10*/
public class CaptchaCacheServiceRedisImpl implements CaptchaCacheService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void set(String key, String value, long expiresInSeconds) {stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);}@Overridepublic boolean exists(String key) {return stringRedisTemplate.hasKey(key);}@Overridepublic void delete(String key) {stringRedisTemplate.delete(key);}@Overridepublic String get(String key) {return stringRedisTemplate.opsForValue().get(key);}@Overridepublic Long increment(String key, long val) {return stringRedisTemplate.opsForValue().increment(key, val);}@Overridepublic String type() {return "redis";}
}
配置
在resources目录新建META-INF.services文件夹,新建文件名为com.anji.captcha.service.CaptchaCacheService,内容为com.qiangesoft.captcha.cache.CaptchaCacheServiceRedisImpl
3.4 登录验证接口
package com.qiangesoft.captcha.controller;import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.qiangesoft.captcha.pojo.LoginDTO;
import com.qiangesoft.captcha.pojo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;/*** 登录接口** @author qiangesoft* @date 2024-05-10*/
@Controller
public class LoginController {@Autowiredprivate CaptchaService captchaService;@GetMapping("/")public String index() {return "login";}@ResponseBody@PostMapping("/login")public ResultVO login(@RequestBody LoginDTO loginDTO) {// 登录二次校验CaptchaVO captchaVO = new CaptchaVO();captchaVO.setCaptchaVerification(loginDTO.getCaptcha());ResponseModel response = captchaService.verification(captchaVO);if (!response.isSuccess()) {throw new RuntimeException("图片验证码校验失败");}// todo 认证逻辑return ResultVO.ok();}}
3.5 vue方式
主要代码如下:
<template><div class="login-bg"><el-form style="width: 500px;height: 40px;margin: auto"><el-form-item label="账号" prop="username"><el-input v-model="username" placeholder="账号"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="password" placeholder="密码"></el-input></el-form-item><el-form-item><el-button type="primary" @click="checkParam">登录</el-button></el-form-item></el-form><Verifyref="verify"captcha-type="blockPuzzle":img-size="{width:'400px',height:'200px'}"@success="login"/><!-- <Verify--><!-- ref="verify"--><!-- captcha-type="clickWord"--><!-- :img-size="{width:'400px',height:'200px'}"--><!-- @success="login"--><!-- />--></div>
</template><script>
import Verify from './../components/verifition/Verify'
import { Message } from 'element-ui'
import { login } from '@/api'export default {components: {Verify},data () {return {username: 'admin',password: '123456'}},beforeDestroy () {document.removeEventListener('keyup', this.handlerKeyup)},created () {document.addEventListener('keyup', this.handlerKeyup)},methods: {handlerKeyup (e) {const keycode = document.all ? event.keyCode : e.whichif (keycode === 13) {this.checkParam()}},checkParam () {if (!this.username || !this.password) {Message.error('请先输入账号密码')}this.$refs.verify.show()},login (params) {login({username: this.username,password: this.password,captcha: params.captchaVerification}).then(res => {const code = res.codeif (code === 200) {Message.success('登录成功')this.$router.push('/index')} else {Message.error(res.message)}})}}
}
</script>
3.6 html方式
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置静态资源
package com.qiangesoft.captcha.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 全局异常处理** @author qiangesoft* @date 2024-03-19*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");}
}
html代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport"content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/><title>verify插件demo</title><link rel="stylesheet" type="text/css" th:href="@{/static/css/verify.css}"><script>if (!window.Promise) {document.writeln('<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.min.js"><' + '/' + 'script>');}</script><style>.btn {border: none;outline: none;width: 110px;height: 40px;line-height: 40px;text-align: center;cursor: pointer;background-color: #409EFF;color: #fff;font-size: 16px;}</style>
</head><body>
<div class="box"><h3>用户登录</h3>账号:<input type="text" id="username" placeholder="账号" value="admin"/> <br/><br/>密码:<input type="password" id="password" placeholder="密码" value="123456"/><br/><br/><button class="btn" id='btn'>滑块登录</button><button class="btn" id='btn1'>文字登录</button><div id="mpanel" style="margin-top:50px;"></div><div id="mpanel1" style="margin-top:50px;"></div>
</div><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/1.9.1/jquery.js"></script>
<script th:src="@{/static/js/crypto-js.js}"></script>
<script th:src="@{/static/js/ase.js}"></script>
<script th:src="@{/static/js/verify.js}"></script><script>// 滑块$('#mpanel').slideVerify({baseUrl: 'http://localhost:8028',mode: 'pop',containerId: 'btn',imgSize: {width: '400px',height: '200px',},barSize: {width: '400px',height: '40px',},// 检验参数合法性的函数,mode ="pop"有效beforeCheck: function () {// todo 可进行参数校验return true},// 加载完毕的回调ready: function () {},// 成功的回调success: function (params) {const username = $("#username").val();const password = $('#password').val();$.ajax({url: '/login',type: 'post',contentType: 'application/json',dataType: 'json',data: JSON.stringify({"username": username,"password": password,"captcha": params.captchaVerification}),success: function (res) {if (res.code === 200) {alert("登录成功");} else {alert(res.message);}},error: function (e) {alert('请求失败')}})},// 失败的回调error: function () {}});// 文字点击$('#mpanel1').pointsVerify({baseUrl: 'http://localhost:8028',containerId: 'btn1',mode: 'pop',imgSize: {width: '400px',height: '200px',},beforeCheck: function () {return true},ready: function () {},success: function (params) {const username = $("#username").val();const password = $('#password').val();$.ajax({url: '/login',type: 'post',contentType: 'application/json',dataType: 'json',data: JSON.stringify({"username": username,"password": password,"captcha": params.captchaVerification}),success: function (res) {if (res.code === 200) {alert("登录成功");} else {alert(res.message);}},error: function (e) {alert('请求失败')}})},error: function () {}});
</script>
</body></html>
四、测试
4.1 接口调用
依赖中默认接口
功能 | 描述 | 请求方式 |
---|---|---|
获取验证码 | /captcha/get | post |
核对验证码 | /captcha/check | post |
接口调用流程
获取验证码接口:/captcha/get
请求参数
{"captchaType": "blockPuzzle", // 验证码类型 clickWord"clientUid": "唯一标识" // 客户端UI组件id,组件初始化时设置一次,UUID(非必传参数)
}
响应数据
{"repCode": "0000","repData": {"originalImageBase64": "底图base64","point": { // 默认不返回的,校验的就是该坐标信息,允许误差范围"x": 205,"y": 5},"jigsawImageBase64": "滑块图base64","token": "71dd26999e314f9abb0c635336976635", // 一次校验唯一标识"secretKey": "16位随机字符串", // aes秘钥,开关控制,前端根据此值决定是否加密"result": false,"opAdmin": false},"success": true,"error": false
}
核对验证码接口:/captcha/check
请求参数
{"captchaType": "blockPuzzle","pointJson": "QxIVdlJoWUi04iM+65hTow==", // aes加密坐标信息"token": "71dd26999e314f9abb0c635336976635" // get请求返回的token
}
响应数据
{"repCode": "0000","repData": {"captchaType": "blockPuzzle","token": "71dd26999e314f9abb0c635336976635","result": true,"opAdmin": false},"success": true,"error": false
}
4.2 vue
4.3 html
五、源码地址
码云:https://gitee.com/qiangesoft/boot-business/tree/master/boot-business-captcha
方便的话博客点个小心心,码云仓库点个star呗!!!
相关文章:

SpringBoot+Vue实现图片滑块和文字点击验证码
一、背景 1.1 概述 传统字符型验证码展示-填写字符-比对答案的流程,目前已可被机器暴力破解,应用程序容易被自动化脚本和机器人攻击。 摒弃传统字符型验证码,采用行为验证码采用嵌入式集成方式,接入方便,安全&#…...

每日复盘-20240515
仅用于记录当天的市场情况,用于统计交易策略的适用情况,以便程序回测 短线核心:不参与任何级别的调整,采用龙空龙模式 一支股票 10%的时候可以操作, 90%的时间适合空仓等待 国联证券 (1)|[9:25]|[133765万]|31.12 一…...

【Android】Apk图标的提取、相同目录下相同包名提取的不同图标apk但是提取结果相同的bug解决
一般安卓提取apk图标我们有两种常用方法: 1、如果已经获取到 ApplicationInfo 对象(假设名为 appInfo),那么我们获取方法为: appInfo.loadIcon(packageManager)// 返回一个 Drawable 对象2、 如果还没获取到 Applica…...

高校普法|基于SSM+vue的高校普法系统的设计与实现(源码+数据库+文档)
高校普法系统 目录 基于SSM+vue的高校普法系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3律师功能模块 4学生功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获…...

pytest教程-47-钩子函数-pytest_sessionfinish
领取资料,咨询答疑,请➕wei: June__Go 上一小节我们学习了pytest_sessionstart钩子函数的使用方法,本小节我们讲解一下pytest_sessionfinish钩子函数的使用方法。 pytest_sessionfinish 钩子函数在 Pytest 测试会话结束时调用,…...

如何使用Python下载哔哩哔哩(Bilibili)视频字幕
在本文中,我将向大家展示如何使用Python下载哔哩哔哩(Bilibili)视频的字幕。通过这个方法,你可以轻松地获取你喜欢的视频的字幕文件,方便学习和交流。 准备工作 在开始之前,我们需要安装一些必要的库&…...

IP代理网络协议介绍
在IP代理页面上,存在HTTP/HTTPS/Socks5三种协议。它们都是客户端与服务器之间交互的协议。 HTTP HTTP又称之为超文本传输协议,在因特网使用范围广泛。它是一种请求/响应模型,客户端向服务器发送请求,服务器解析请求后对客户端作出…...
渗透相关面试+流量分析
文章目录 简单自我介绍上一个工作的主要内容Hvv的分组和流程你在hvv/攻防演练中取得了哪些成绩? 二、渗透相关面试题基础端口号以及入侵方式OSI七层协议响应状态码都有哪些?**WAF和IPS的区别**盲注是什么?java内存马类型**内存马有几种类型**…...

Shell之高效文本处理命令
目录 一、排序命令—sort 基本语法 常用选项 二、去重命令—uniq 基本语法 常用选项 三、替换命令—tr 基本语法: 常用选项 四、裁剪命令—cut 基本语法: 常用选项 字符串分片 五、拆分命令—split 基本语法: 六、 文件…...

u3d的ab文件注意事项
//----------------LoadAllAB.cs--------------------- using System.Collections;using UnityEngine;namespace System.IO{public class LoadAllAB : MonoBehaviour{ //读取本地string path "Assets/Actors/lznh/ab/animation/t_bl/";// Use this for initiali…...

Go微服务开源框架kratos的依赖注入关系总结
该文章为学习开源微服务框架kratos的学习笔记!官方文档见:简介 | Kratos Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关框架及工具。 一、Kratos 项目结构简介 通过 Kratos 工具生成的 Go工程化项目模板如下: applicati…...
Linux 第三十二章
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C,linux 🔥座右铭:“不要等到什么都没有了…...
手机号码的正则表达式
手机号码的正则表达式会根据不同的国家/地区有所不同,因为每个国家/地区都有自己特定的手机号码格式。但是,我可以为你提供一个通用的正则表达式模板,并给出一些具体国家/地区的例子。 通用模板 一个基本的手机号码正则表达式模板可能如下所…...

机器学习入门介绍
各位大佬好 ,这里是阿川的博客 , 祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 目录 三大方向机器学习产生的原因机器如何学习…...

一文说通用户故事点数是什么?
一文说通用户故事点数是什么? 第26期:一文说通用户故事点数是什么? 用户故事点数是一种采用相对估算法进行估算的一种工具,一般采用斐波那契数列表征用户故事里说的大小,采用0 1 2 3 5 8 13这样的一些数字来表征用户…...

GAME101-Lecture07学习
前言 今天主要讲shading(着色)。在讲着色前,要先讲图形中三角形出现遮挡问题的方法(深度缓存或缓冲)。 先采样再模糊错误:对信号的频谱进行翻译(在这期间会有频谱的混叠)ÿ…...

【一步一步了解Java系列】:了解Java与C语言的运算符的“大同小异”
看到这句话的时候证明:此刻你我都在努力~ 加油陌生人~ 个人主页: Gu Gu Study 专栏:一步一步了解Java 喜欢的一句话: 常常会回顾努力的自己,所以要为自己的努…...
ICSE docker related research
ICSE 2024 Empirical Study of the Docker Smells Impact on the Image Size Docker 气味对镜像大小影响的实证研究 Docker 是一种广泛采用的打包和部署应用程序的工具,它利用 Dockerfile 来构建镜像。然而,创建最佳的 Dockerfile 可能具有挑战性&…...

【C++】学习笔记——多态_1
文章目录 十二、继承8. 继承和组合 十三、多态1. 多态的概念2. 多态的定义和实现虚函数重写的两个特殊情况override 和 final 3. 多态的原理1. 虚函数表 未完待续 十二、继承 8. 继承和组合 我们已经知道了什么是继承,那组合又是什么?下面这种情况就是…...

C++map容器关联式容器
Cmap 1. 关联式容器 vector、list、deque、forward_list(C11)等STL容器,其底层为线性序列的数据结构,里面存储的是元素本身,这样的容器被统称为序列式容器。而map、set是一种关联式容器,关联式容器也是用来存储数据的࿰…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...

stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...

工厂方法模式和抽象工厂方法模式的battle
1.案例直接上手 在这个案例里面,我们会实现这个普通的工厂方法,并且对比这个普通工厂方法和我们直接创建对象的差别在哪里,为什么需要一个工厂: 下面的这个是我们的这个案例里面涉及到的接口和对应的实现类: 两个发…...

timestamp时间戳转换工具
作为一名程序员,一款高效的 在线转换工具 (在线时间戳转换 计算器 字节单位转换 json格式化)必不可少!https://jsons.top 排查问题时非常痛的点: 经常在秒级、毫秒级、字符串格式的时间单位来回转换,于是决定手撸一个…...
JS的传统写法 vs 简写形式
一、条件判断与逻辑操作 三元运算符简化条件判断 // 传统写法 let result; if (someCondition) {result yes; } else {result no; }// 简写方式 const result someCondition ? yes : no;短路求值 // 传统写法 if (condition) {doSomething(); }// 简写方式 condition &…...
【免杀】C2免杀技术(十五)shellcode混淆uuid/ipv6/mac
针对 shellcode 混淆(Shellcode Obfuscation) 的实战手段还有很多,如下表所示: 类型举例目的编码 / 加密XOR、AES、RC4、Base64、Poly1305、UUID、IP/MAC改变字节特征,避开静态签名或 YARA结构伪装PE Stub、GIF/PNG 嵌入、RTF OLE、UUID、IP/MAC看起来像合法文件/数据,弱…...

【版本控制】Git 和 GitHub 入门教程
目录 0 引言1 Git与GitHub的诞生1.1 Git:Linus的“两周奇迹”,拯救Linux内核1.2 GitHub:为Git插上协作的翅膀1.3 协同进化:从工具到生态的质变1.4 关键历程时间轴(2005–2008) 2 Git与GitHub入门指南2.1 Gi…...
Java中List的forEach用法详解
在 Java 中,List.forEach() 是 Java 8 引入的一种简洁的遍历集合元素的方法。它基于函数式编程思想,接受一个 Consumer 函数式接口作为参数,用于对集合中的每个元素执行操作。 基本语法 java 复制 下载 list.forEach(consumer); 使用示…...