短信防刷之滑动验证码

2024-04-16 23:36
文章标签 滑动 验证码 短信 防刷

本文主要是介绍短信防刷之滑动验证码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:最近想写一个滑动验证码,前台的样式虽然很好看,但是并不安全,网上也都是一些demo,不是前后台分离的,然后就自己查资料,自己来完成了

滑动验证码

一、为什么要使用滑动验证码

首先,滑块验证码能够有效防止暴力破解和自动化攻击。在传统的账号密码验证方式下,黑客可以通过暴力破解手段尝试大量密码组合,从而获取用户账户的控制权。而滑块验证码的引入,使得每次验证都需要用户进行手动操作,极大地增加了黑客攻击的难度和成本。

其次,滑块验证码能够提升用户体验。相比于传统的文字或数字验证码,滑块验证码的操作更加简单直观,用户只需要通过拖动滑块即可完成验证,无需输入复杂的字符或数字。这不仅降低了用户的使用门槛,也提升了用户的操作体验。

此外,滑块验证码还具有一定的灵活性和可扩展性。淘宝等电商平台可以根据自身的安全需求和用户行为数据,动态调整滑块验证码的难度和出现频率。例如,在检测到异常登录行为或高风险操作时,平台可以要求用户进行更严格的滑块验证,以确保账户安全。

总的来说,淘宝频繁出现滑块验证码是为了保障用户账户的安全性和提升用户体验。通过引入这种交互式的验证方式,淘宝能够有效地防止自动化攻击和恶意操作,同时为用户提供更加便捷和安全的购物环境。

二、实现原理

滑动验证码的原理基于人类的视觉和手势行为。它的设计目的是模拟人类在拖动滑块上的操作,以判断用户是否为真实的人类。下面是滑动验证码的工作原理:

加载验证码:当用户访问需要进行验证的网站时,验证码会被加载并显示在页面上。

定位滑块图:通过分析页面上的验证码元素,识别出背景图、滑块图和验证区域。通常,滑块图会被嵌入到背景图中,而验证区域则是滑块图与背景图的重叠部分。

用户滑动操作:用户需要使用鼠标或触摸屏对滑块图进行拖动操作,将滑块图滑动至缺口位置,以完成验证。

验证结果判断:当用户完成滑动操作后,系统会根据滑块图的位置与缺口位置的关系来判断验证结果。如果滑块图与缺口位置匹配,系统会认定用户为真实的人类用户;否则,系统会认为用户可能是机器人或恶意攻击者。

也就是以下的流程

  1. 前端显示:用户首先看到一个页面,页面上显示了一个带有滑块和背景图案的验证码区域。通常滑块初始位置在左侧,用户需要将滑块拖动到正确的位置。

  2. 交互过程

    • 用户点击并按住鼠标左键,拖动滑块至指定位置。
    • 前端会监测鼠标移动事件,实时更新滑块的位置。
    • 用户释放鼠标左键后,前端会发送包含滑块位置信息的请求给后端进行验证。
  3. 后端验证

    • 后端接收到前端发送的请求,获取到滑块的位置信息。
    • 后端根据预先生成的验证码信息,对比用户拖动滑块后的位置是否与预期位置相符。
    • 如果位置匹配成功,则验证通过;否则验证失败,要求用户重新验证。
  4. 防御机制:滑块验证码通常会具备一些防御机制来防止恶意破解,例如检测拖动速度、检测鼠标轨迹等,以提高安全性。

三、代码实现

这里要感谢gitee作者-天爱有情开源的后台代码 captcha

3.1 后台代码

3.1.1依赖
        <dependency><groupId>cloud.tianai.captcha</groupId><artifactId>tianai-captcha</artifactId><version>1.4.1</version></dependency><dependency><groupId>cloud.tianai.captcha</groupId><artifactId>tianai-captcha-springboot-starter</artifactId><version>1.4.1</version></dependency>
3.1.2 yml配置

作者这里使用的配置文件是yml的格式,小伙伴们一定要注意格式哦!!

captcha:cache:enabled: truecache-size: 20secondary:enabled: falseinit-default-resource: false
cors:control-allow-headers: "*"control-allow-methods: "*"control-allow-origin: "*"
3.1.3控制层代码
package com.brook.controller;import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.spring.application.ImageCaptchaApplication;
import cloud.tianai.captcha.spring.plugins.secondary.SecondaryVerificationApplication;
import cloud.tianai.captcha.spring.vo.CaptchaResponse;
import cloud.tianai.captcha.spring.vo.ImageCaptchaVO;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import com.brook.common.result.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
@RestController
@RequestMapping("/captcha")
public class CaptchaController {@Autowiredprivate ImageCaptchaApplication imageCaptchaApplication;/作者这里只写了滑动验证码,所以只匹配了滑块的类型@GetMapping("/gen")@ResponseBodypublic Result<CaptchaResponse<ImageCaptchaVO>> genCaptcha(HttpServletRequest request, @RequestParam(value = "type", required = false)String type) {if (StringUtils.isBlank(type)) {type = CaptchaTypeConstant.SLIDER;}if ("RANDOM".equals(type)) {type = CaptchaTypeConstant.SLIDER;}CaptchaResponse<ImageCaptchaVO> response = imageCaptchaApplication.generateCaptcha(type);return Result.success(response);}/校验x轴的坐标,以及其他的一些信息@PostMapping("/check")@ResponseBodypublic Result<ApiResponse<?>> checkCaptcha(@RequestBody Data data,HttpServletRequest request) {ImageCaptchaTrack dataData = data.getData();ApiResponse<?> response = imageCaptchaApplication.matching(data.getId(), dataData);boolean success = response.isSuccess();System.out.println(success);if (response.isSuccess()) {return Result.success(ApiResponse.ofSuccess(Collections.singletonMap("id", data.getId())));}return Result.success(response);}@lombok.Datapublic static class Data {private String  id;private ImageCaptchaTrack data;}/*** 二次验证,一般用于机器内部调用,这里为了方便测试,作者这里没有使用到* @param id id* @return boolean*/@GetMapping("/check2")@ResponseBodypublic boolean check2Captcha(@RequestParam("id") String id) {// 如果开启了二次验证if (imageCaptchaApplication instanceof SecondaryVerificationApplication) {return ((SecondaryVerificationApplication) imageCaptchaApplication).secondaryVerification(id);}return false;}
}
3.1.4 跨域代码
package com.brook.controller;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.util.pattern.PathPatternParser;import java.util.List;/*** 该类用于设置跨域*/
@Configuration
public class CorsPropConfiguration {@Beanpublic FilterRegistrationBean coreWebFilter(CorsProperties corsProperties) {CorsConfiguration config = new CorsConfiguration();// * 号表示匹配任意的config.setAllowedMethods(corsProperties.getControlAllowMethods());config.setAllowedOrigins(corsProperties.getControlAllowOrigin());config.setAllowedHeaders(corsProperties.getControlAllowHeaders());PathPatternParser patternParser = new PathPatternParser();UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(patternParser);// ** 代表所有source.registerCorsConfiguration("/**", config);FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));bean.setOrder(0);return bean;}@Data@Configuration@ConfigurationProperties(prefix = "cors")public static class CorsProperties {private List<String> controlAllowHeaders;private List<String> controlAllowMethods;private List<String> controlAllowOrigin;}
}
3.1.5 负责模版和背景图,小伙伴也可以自行添加图片

像这样,把图片整合进resources下的bgimages包中,就可以啦,不过要加这行代码

注:图片名称一定要和自己整合的图片一致!!!并且图片的大小要设置为600×360

addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/m.jpg","default"));
package com.brook.controller;import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant;
import cloud.tianai.captcha.generator.impl.StandardSliderImageCaptchaGenerator;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
import cloud.tianai.captcha.resource.impl.DefaultResourceStore;
import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
import org.springframework.stereotype.Component;import static cloud.tianai.captcha.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;/*** @Author: YanShuLing* @date 2022/7/11 14:22* @Description 负责模板和背景图存储的地方*/
@Component
public class MyResourceStore extends DefaultResourceStore {public MyResourceStore() {// 滑块验证码 模板 (系统内置)ResourceMap template1 = new ResourceMap("default",4);template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));ResourceMap template2 = new ResourceMap("default",4);template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));// 1. 添加一些模板addTemplate(CaptchaTypeConstant.SLIDER, template1);addTemplate(CaptchaTypeConstant.SLIDER, template2);// 2. 添加自定义背景图片addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/a.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/b.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/c.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/d.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/e.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/f.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/g.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/h.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/i.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/j.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/k.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/l.jpg","default"));addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/m.jpg","default"));}
}

3.2 vue前台代码

(作者这里使用的是vue2 从gitee上面拉取的花裤衩脚手架)小伙伴们也可以使用正常的vue2

3.2.1 首先定义一个子组件

中间运行可能需要安装一个依赖,小伙伴运行的时候,按照控制台的提示安装就好了

<template><div class="slider" ref="sliderComponent"><div class="mask"><div class="container"><div class="title"><div class="text"><span>请完成下列验证后继续</span></div><div class="button-group"><imgsrc="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE2UlEQVRoQ+1YXWwUVRT+zsw2Qktn1ljatEjjD+4WJILpg0rE4ItGn0QMyGMTE/kJTUwFElt2ZmopQWhIMNjyRKIPiH/hScXEiJEgL/4mamcwUSG2UCvuzAJt7e4cc6ddFaTMHTq70qTztnvPPef7zt899xJm+EczHD9mCfzfEZyNQNwRaD7IFSPnLy/FbZWnv99MF8P033QRSFvuDmbuJOBL20w2zzwCpnuEwWtnLIGU6X4O8IMEHLXN5OqyRcBgVg7vzK1AwX+KGM0ANQDcwAQVwAVi+oWAU6Ti46Vp7djba6lwLXBp0/2VwQ0getUx9NaSE1h1iOcMns21ss9tANeGGRTrBDrHCnrnzNV6vt1Kl4p7RAHnBr1RgBVFUbb2Z7S9YfqmVcRNVm6Nj8I+MBYGwAiXmHFMIXzkg85UkDpQQD7BwN0M3AWfloGwGsy3FIlARYu9Q/9Q/G7aNXKHPzb2U7CmYJ2dSb5VEgLMTGkr1wX4LwUGiM4T2Kpv1A8db6HR6xm9vzs3f2S88JwPbAZjAYgKBN5iG8nexV25Rwr5wqdiv5qgh37o0E/FTkCAb+r0DjPzukkv9qnzta0yPfvfYB7Yz9ofF9xDAJ4W/ytE+3wVXyHPrwd651YssLdXDcROIGV6O4XnCZQHcavw3PWMLOn0VvrghUQ8xJw4n1AxtPiequFiETdZXpsP3g1mlUAnGbyCiMafzWhzLCI/VgJBznPhncl83xQGvvkgV14852WZueJKIOSD8DuAIYIgRrcCvLwoQ8DPtpm8Mwz8RAZIfqLbDJxxHVGwBOqzTX2jzNa06R5gQjNNdKhaZlSF76MTjqmvDJeLQCDd6W1j398tCjZRoy2KmvNFMPft4aqx0dFahf6s85kEoVpivw5Mt4v0AagSRH2OofXERiA4pCxvUPR5IoSmjozhuGSkUijV5T2MvP+Z6PP1jXpNWKuMC5yMHjkCVnYvGG0A3nPM5BoZxeWSkSKQNrOfMLBKIWzoN5IHywVOxo4kAddmcApETzqG/oGM4huVEfX2puVZTHAcQ38jTI8kgWyOgXkVSmL5d5l534Qpnc76vbvcReNjfJqAy7aZDG25Nx+BzovLxv3810TI2UZSC3OGJIHypVDKcp8A8/sEcmxTT8dEoHxF3GRln/cZfQQct83ko7EQSJWxjabM7LvBhErocYzki/EQKNNBFtzuzrjDwbyUUFY6HdqJWAiUa5RIW9mNzHgNoKH1hlYf6zgd1zA3lUeXHOB5+WHvRzDXkaJstzPaK2Hev/FxmqjXNvRNMgZkZdKm28vgDSCcbWjUU7LzllQbLYK44kKjYKOdSfbJArye3D+pI66W6jP9RrUoZKkvEgGhMW25XczcHlwpFd4yXRICPJj2MzgBKN2OqbVLIZ8UikzgP5d6ol61RtsW9YIjcr7wm7cnSJvgYYOO9Ge09UTEJSUglE+SeFlEIjAW4VllslW2MMgQBTsBVum2jeqOqOAjFfG1vCLzsCX2jXOhQQE3+ozHiPD43/diwlkF6gtRcv5qHJFT6GoFN/K0KPo8KdRTv7B6v2y3mSqtpk2gqHiqx92JdRoAeIAJX0BVjq5vrz4pc0jJ1EJsBGSMlUJmlkApvBpF52wEonirFLKzESiFV6PonI1AFG+VQvYvqQFST/EC5cgAAAAASUVORK5CYII="@click="reset"/><imgsrc="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAB3klEQVRoQ+2Yy0oEMRBFz/hABf0Gda2gO9349W50p6Br9RsUVHxS0JEwZCaV5EZp7IFedbpyT91KUpMZI//NRq6fCeCvHZwcmBxozMC/KqFV4BP4akya53Ob68Mz0OvAPrALvAA3wJMneMWYbeAQ2AQegLtcDA+AZeMsCvQGXHeAMPFHwHo013nOCQ+AjTkZshJiqyFS4s3ty1zJegBMtE1wDKxF2XkHrgROpMRbbHP5UVFCIUYPiCbxJszrQA+IZvE1AKpyWuSmq2zisip1QOGETHytAy0Q8nVU60AOIlUKcvGtDpRA7AyHlHwbbnUghpg/RcNebmPsnVy8yoEchL3vIl4NELbYeSfiXU91ev/EVJVQLDJ1QNl7ufgeDljM1IINAMUHlbIXysVaJj58627SPJOpHUhl3tpu+8U9vhRCtQYWibeSCdtoFwgFwDLx4a+npPNMlVUrgEd87pxoWtgtACXiu0HUAtSI7wJRA9AiXg5RCqAQL4UoAVCKl0F4AXqIl0B4AGzMKbAR7cO/cbH1ClwoLrZGf7Void8bnmfgVnAbt6hXsxP7ANgC7odnaV/nKaEQYGW4Xvc2ii3j3HOVALQI6vbtBNAttc7AkwPORHUbNjnQLbXOwKN34BvKiqMxJwSPZAAAAABJRU5ErkJggg=="@click="close"/></div></div><div class="img"><div class="backgroup-img"><imgclass="inner-bg-img":src="backgroupImg"/></div><divclass="move-img":style="{left: `${moveX}px`}"><imgclass="inner-mv-img":src="moveImg"/></div></div><div class="slide"><divclass="slider-mask":style="{width: `${blcokLeft}px`}"><divclass="block"ref="block"@mousedown="start":style="{left: `${blcokLeft}px`}"><span class="yidun_slider_icon"></span></div></div></div><divclass="loading"v-if="loading"><span>loading...</span></div></div></div></div>
</template><script>
// =========================================
// 父组件需要提供的方法 名称
// =========================================/*** 获取滑块图片方法*/
const GET_IMG_FUN = 'getCaptcha'
/*** 校验滑块图片方法*/
const VALID_IMG_FUN = 'validImg'
/*** 滑块窗口关闭事件监听*/
const CLOST_EVENT_FUN = 'close'
// import moment from 'moment'
export default {data() {return {/**滑块背景图片 */backgroupImg: '',/**滑块图片 */moveImg: '',/**是否已经移动滑块 */startMove: false,/**滑块移动距离 */blcokLeft: 0,/**开始滑动的x轴 */startX: 0,/**划过的百分比 */movePercent: 0,/**验证码唯一ID */uuid: '',/**滑块移动的x轴 */moveX: 0,/** 加载遮罩标识 */loading: false,Data: {data: {bgImageWidth: null,bgImageHeight: null,sliderImageWidth: null,sliderImageHeight: null,startSlidingTime: null,endSlidingTime: null,trackList: []},id: null}}},props: {// 是否开启日志, 默认truelog: {type: Boolean,required: false,default: true}},mounted() {this.getCaptcha()},methods: {/*** 打印日志*/printLog(msg, ...optionalParams) {if (this.log) {if (optionalParams && optionalParams.length > 0) {console.info(`滑块验证码[${msg}]`,optionalParams.length === 1 ? optionalParams[0] : optionalParams)} else {console.info(`滑块验证码[${msg}]`)}}},/*** 获取滑块图片*/getCaptcha() {this.loading = truethis.$emit(GET_IMG_FUN, data => {this.loading = falseif (!data) returnthis.backgroupImg = data.captcha.backgroundImagethis.moveImg = data.captcha.templateImagethis.Data.data.bgImageWidth = 280this.Data.data.bgImageHeight = 180this.Data.data.sliderImageHeight = 180this.Data.data.sliderImageWidth = 52this.uuid = data.idthis.Data.id = data.id})},/*** 校验图片*/validImg(Data) {this.printLog(`滑块抬起`, this.movePercent)this.$emit(VALID_IMG_FUN, Data, data => {// this.printLog(VALID_IMG_FUN, data)console.log(data)if (data.success === false) {this.reset()return}this.close()})},/*** 重新生成图片*/reset() {this.getCaptcha()this.moveX = 0this.movePercent = 0this.startX = 0this.blcokLeft = 0},/*** 按钮关闭事件*/close() {this.printLog('关闭按钮触发')this.$emit(CLOST_EVENT_FUN)},/*** 开始滑动*/start(e) {this.startX = e.pageXthis.startMove = truewindow.addEventListener('mousemove', this.move)window.addEventListener('mouseup', this.up)},/*** 滑块滑动事件*/move(e) {if (!this.startMove) return// this.Data.data.startSlidingTime = moment(new Date()).format('ddd MMM DD HH:mm:ss [CST] YYYY')this.Data.data.startSlidingTime = new Date()const moveX = e.pageX - this.startXconst movePercent = moveX / 280if (moveX <= 0) {this.blcokLeft = 0this.moveX = 0this.movePercent = 0} else if (moveX >= 0 && moveX <= 235) {this.blcokLeft = moveXthis.moveX = moveXthis.movePercent = movePercent} else if (moveX >= 235) {this.blcokLeft = 235this.moveX = 235this.movePercent = movePercent}// const track = {//   x: this.blcokLeft,//   y: e.pageY,//   t: (this.Data.data.endSlidingTime - this.Data.data.startSlidingTime)// }const track = {x: this.blcokLeft,y: 0,t: 0 // 初始设为0,稍后在 up 方法中更新为正确的时间间隔}if (track.x === 0) {return}this.Data.data.trackList.push(track)},/*** 滑块鼠标抬起事件*/up(e) {window.removeEventListener('mousemove', this.move)window.removeEventListener('mouseup', this.up)if (!this.startMove) return// this.Data.data.endSlidingTime = moment(new Date()).format('ddd MMM DD HH:mm:ss [CST] YYYY')this.Data.data.endSlidingTime = new Date()this.Data.data.trackList.forEach(track => {const endTime = this.Data.data.endSlidingTime.getTime()const startTime = this.Data.data.startSlidingTime.getTime()track.t = endTime - startTime})this.startMove = falsethis.Data.data.endSlidingTime = new Date()this.validImg(this.Data)}},/*** 销毁事件*/beforeDestroy() {window.removeEventListener('mousemove', this.move)window.removeEventListener('mouseup', this.up)}
}
</script><style lang="scss" scoped>
.slider-mask {position: absolute;left: 0;top: 0;height: 40px;border: 0 solid #1991fa;background: #d1e9fe;border-radius: 2px;
}.yidun_slider_icon {position: absolute;top: 50%;margin-top: -6px;left: 50%;margin-left: -6px;width: 14px;height: 10px;background-image: url(https://cstaticdun.126.net//2.13.7/images/icon_light.4353d81.png);background-position: 0 -13px;background-size: 32px 544px;
}.inner-mv-img,
.inner-bg-img,
.title {-moz-user-select: none;-webkit-user-select: none;-ms-user-select: none;-khtml-user-select: none;user-select: none;
}.slider {.mask {display: block;z-index: 998;background: rgba(0, 0, 0, 0);width: 310px;height: 280px;}.container {position: absolute;z-index: 999;width: 310px;height: 280px;margin: auto;background: rgba(255, 255, 255, 1);border-radius: 6px;box-shadow: 0px 0px 11px 0px rgba(153, 153, 153, 1);box-sizing: border-box;padding: 17px 15px;.title {font-size: 14px;color: #333;display: flex;justify-content: space-between;.button-group {img {width: 25px;height: 25px;cursor: pointer;}}}.img {width: 280px;height: 180px;position: relative;img {width: 100%;}.backgroup-img {position: absolute;left: 0;top: 0;width: 100%;}.move-img {width: 52.20338981px;position: absolute;left: 0;top: 0;}}.slide {width: 100%;height: 40px;border: 1px solid #e4e7eb;background-color: #f7f9fa;box-sizing: border-box;position: relative;&::before {position: absolute;content: "按住左边按钮移动完成上方拼图";display: flex;justify-content: center;align-items: center;font-size: 12px;color: #999;width: 100%;height: 100%;text-indent: 50px;}.block {width: 40px;height: 38px;background-color: #fff;box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);display: flex;justify-content: center;align-items: center;position: absolute;left: 0;top: 0;cursor: pointer;background-size: 30px;background-repeat: no-repeat;background-position: center;}}.block:hover {background-color: #1991fa;}.block:hover .yidun_slider_icon {background-image: url(https://cstaticdun.126.net//2.13.7/images/icon_light.4353d81.png);background-position: 0 0;background-size: 32px 544px;}.loading {width: 100%;height: 100%;background: rgba(0, 0, 0, 0.3);position: absolute;top: 0;left: 0;border-radius: 6px;display: flex;justify-content: center;align-items: center;color: #fff;}}
}
</style>

注:一定要对照好参数,不然传到后台会报错!!!

3.2.2 父组件

(作者这里是一个注册的页面,当用户点击发送验证码的时候,滑动验证码就出现了)

小伙伴可以根据自己的需求更改代码

<template><div class="login-container"><div style="width: 450px; height: 400px; margin: 150px auto; background-color:rgba(165,190,234,0.5); border-radius: 10px"><div style="width: 100%; height: 100px; font-size: 30px; line-height: 100px; text-align: center; color: #5050bb">欢迎注册</div><div style="margin-top: 25px; text-align: center; height: 320px;"><el-form :model="UserRegisterReq"><el-form-item label="手机号:" label-width="100px"><el-input v-model="UserRegisterReq.phone" prefix-icon="el-icon-user" style="width: 80%" placeholder="请输入手机号"></el-input></el-form-item><el-form-item label="验证码:" label-width="100px"><el-input v-model="UserRegisterReq.code" prefix-icon="el-icon-user" style="width: 80%" placeholder="请输入验证码"></el-input></el-form-item><el-form-item label="密码:" label-width="100px"><el-input v-model="UserRegisterReq.password" show-password prefix-icon="el-icon-lock" style="width: 80%" placeholder="请输入密码"></el-input></el-form-item><el-form-item label="确认密码:" label-width="100px"><el-input v-model="UserRegisterReq.confirmPassword" show-password prefix-icon="el-icon-lock" style="width: 80%" placeholder="确认密码"></el-input></el-form-item><el-form-item><el-button type="success" @click="onShow()">点击发送验证码</el-button><el-button style="width: 50%; margin-top: 10px" type="primary" @click="register()">注册</el-button></el-form-item><div style="text-align: center"><span style="font-size: 15px">已有账号</span>?<a href="javascript:void(0)" style="text-decoration: none; font-size: 15px;" @click="navLogin">点击登录</a></div></el-form><center><div v-if="show"><Sliderref="sliderComponent":captchaData="captchaData"@getCaptcha="handleGetCaptcha"@validImg="validImg"@close="onClose":log="true"></Slider></div> </center></div></div></div>
</template>
<script>
import { check, getCaptcha, send, sign } from '@/api/user'
import Slider from '@/views/register/slider.vue'
// import {error} from 'autoprefixer/lib/utils'
export default {components: {Slider},data() {return {UserRegisterReq: {confirmPassword: ''},Data: {ImageCaptchaTrack: {},id: ''},show: false,type: 'RANDOM',captchaData: null}},// 页面加载的时候,做一些事情,在created里面created() {// window.localStorage.setItem('isRegisterPage', 'true')// console.log('注册页面' + window.localStorage.getItem('isRegisterPage'))},// 定义一些页面上控件出发的事件调用的方法mounted() {},methods: {sendCode() {send(this.UserRegisterReq.phone).then(response => {alert('验证码是:' + response.data)})},navLogin() {this.$router.push('/login')},register() {sign(this.UserRegisterReq).then(response => {if (response.code === 200) {alert(response.msg)}})// 注册逻辑},onClose() {this.show = false},onShow() {this.show = true},handleGetCaptcha(callback) {getCaptcha(this.type).then(response => {// console.log(response)callback(response.data)})},validImg(data, callback) {// console.log(data)check(data).then(response => {// console.log(response)callback(response.data)if (response.data.success) {// this.onClose()this.sendCode()// location.reload()} else {alert('验证失败')this.handleGetCaptcha()// 验证失败时重新获取验证码}}).catch(error => {console.error(error)}).finally(() => {this.loading = false // 请求结束,设置loading为false})}}
}
</script>
<style scoped>
.login-container {height: 100vh;overflow: hidden;background-image: url('~@/assets/404_images/login_bg.jpg');background-size: 100%;display: flex;align-items: center;justify-content: center;
}
ul li {list-style: none;
}
* {margin: 0;padding: 0;
}
.top{overflow: auto;
}
.top li:hover{cursor: pointer;
}
.top li{float: left;height: 40px;width: 120px;margin-right: 5px;line-height: 40px;text-align: center;background-color: #409eff;color: #fff;font-size: 15px;box-sizing: border-box;border: 1px solid #409eff;
}
.captcha-iframe {width: 300px;height: 320px;border: none;
}
.after {color: #88949d;
}
</style>

好了代码到这里也就基本结束了

3.3 过程中出现的问题

这里讲一个作者在完成滑动验证码时遇到的一个问题,有小伙伴可能会使用springcloud,要注意,滑块验证码不要跟自己的用户模块写在一起,不然这块可能在gateway网关在redis中到不到自己存在redis中的key和value ,并且可能会出现报错

org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.class

具体原因是:

Spring Boot 已经默认提供了 Redis 的自动配置,导致与我们自定义的 Redis 配置发生冲突,也是这个原因导致我们在网关的过滤器中取不到redis中的key和value,因为存和取不是一个redis实例

下面我们来演示一下效果

验证成功后会发送验证码,验证失败会刷新图片

                         

                       

3.4 为什么要刷新图片呢?

如果不刷新图片,恶意攻击者会通过持续尝试来破解验证码。如果一直使用同一张验证码图片,
攻击者可能会使用自动化工具进行大量尝试,增加系统的风险, 尽管刷新验证码可能会增加用户操作的复杂性,但在一定程度上也可以提高用户体验。
如果用户在第一次尝试时失败,系统刷新验证码后,用户有机会重新尝试,避免了因错误导致的长时间等待或退出

到这里就结束啦,感谢大家的一路支持,请给凌弟一个暴击三连吧!跪谢!!

                                                                

这篇关于短信防刷之滑动验证码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/910211

相关文章

Spring 验证码(kaptcha)

首先引入需要的jar包: <dependency><groupId>com.github.axet</groupId><artifactId>kaptcha</artifactId><version>0.0.9</version></dependency> 配置验证码相关设置: <bean id="captchaProducer" class="com.

专题二_滑动窗口_算法专题详细总结

目录 滑动窗口,引入: 滑动窗口,本质:就是同向双指针; 1.⻓度最⼩的⼦数组(medium) 1.解析:给我们一个数组nums,要我们找出最小子数组的和==target,首先想到的就是暴力解法 1)暴力: 2)优化,滑动窗口: 1.进窗口 2.出窗口 3.更新值 2.⽆重复字符的最⻓⼦串(medium) 1)仍然是暴力解法: 2)优化: 进窗口:hash[s[rig

hot100刷题第1-9题,三个专题哈希,双指针,滑动窗口

求满足条件的子数组,一般是前缀和、滑动窗口,经常结合哈希表; 区间操作元素,一般是前缀和、差分数组 数组有序,更大概率会用到二分搜索 目前已经掌握一些基本套路,重零刷起leetcode hot 100, 套路题按套路来,非套路题适当参考gpt解法。 一、梦开始的地方, 两数之和 class Solution:#注意要返回的是数组下标def twoSum(self, nums: Lis

图片验证码

导入依赖 <dependencies><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency></dependencies> 代码 @Servicepublic class ValidateCodeService

【leetcode详解】考试的最大困扰度(滑动窗口典例)

实战总结: sum += answerKey[right] == c; 经典操作,将判断语句转化为0, 1接收来计数//大问题分解: 对'T'还是'F'做修改, 传参为c//滑动窗口: 遍历, 维护left& right指向 及 c的个数, 更新不知从何下手写代码时:考虑先写好第一次的,然后以此为基础补充代码以适后续情况 题面: 解题感受: 思路总体好想, 实现略有挑战。 思路分析:

使用kaptcha验证码生成工具生成验证码

文章目录 maven引入jar包配置使用前端 maven引入jar包 <!--验证码生成工具--><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency> 配置 /*** Kaptcha验证

【每日一题】LeetCode 2379.得到K个黑块的最少涂色次数(字符串、滑动窗口)

【每日一题】LeetCode 2379.得到K个黑块的最少涂色次数(字符串、滑动窗口) 题目描述 给定一个字符串 blocks,其中每个字符代表一个颜色块,可以是 ‘W’(白色)或 ‘B’(黑色)。你需要找到一个至少包含 k 个连续黑色块的子串。每次操作可以将一个白色块变成黑色块。你的任务是找到至少出现一次连续 k 个黑色块的最少操作次数。 和该题目类似:【每日一题】LeetCode 202

【视频教程】手把手AppWizard轻松制作一个emWin滑动主界面控制框架,任意跳转控制(2024-09-06)

现在的新版AppWizard已经比较好用,用户可以轻松的创建各种项目常规界面。 比如早期创建一个支持滑动的主界面框架,并且可以跳转各种子界面,仅仅界面布局和各种图片格式转换都要花不少时间,而现在使用AppWizard,可以说轻轻松松,毫不费力。 用户唯一要做的就是根据自己的芯片性能做一定的速度优化。 视频: https://www.bilibili.com/video/BV17Rp3eLE

node.js实现阿里云短信发送

效果图 实现 一、准备工作 1、官网直达网址: 阿里云 - 短信服务 2、按照首页提示依次完成相应资质认证和短信模板审核; 3、获取你的accessKeySecret和accessKeyId; 方法如下: 获取AccessKey-阿里云帮助中心 4、获取SignName(签名名称)和 TemplateCode(模板code); 二、代码实现 1、项目结构 【/c