短信防刷之滑动验证码

前言:最近想写一个滑动验证码,前台的样式虽然很好看,但是并不安全,网上也都是一些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.mzph.cn/news/823183.shtml

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

相关文章

斯坦福大学2024年人工智能发展和前景全面分析报告

2024 年指数是斯坦福大学迄今为止最全面的指数&#xff0c;恰逢人工智能对社会的影响力达到前所未有的重要时刻。今年&#xff0c;斯坦福大学扩大了研究范围&#xff0c;更广泛地涵盖人工智能的技术进步、公众对该技术的看法以及围绕其发展的地缘政治动态等基本趋势。 完整详细…

onedrive 清理文件历史版本 节省空间

onedrive 清理文件历史版本以节省空间的操作步骤 起因&#xff1a; 用的好好的onedrive高校教育版&#xff0c;突然在2024年4月2日晚上把空间从1T回收到100G&#xff0c;然后文件爆满&#xff0c;虽然没有把文件都给我删了&#xff0c;但是可能几个月窗口期过去就没文件了。而…

2024年——区块链技术进入全新高度

BTC生态蓬勃发展&#xff0c;以太坊的L1和L2模块化重塑智能合约生态。RAAS&#xff08;区块链即服务&#xff09;、Depin、并行EVM等技术的崛起&#xff0c;为区块链应用提供了更高的性能和可扩展性。以太坊再质押成为焦点。技术创新与日俱进&#xff0c;一同探索这个充满活力的…

KVM部署

1、检查虚拟化支持 首先&#xff0c;确认你的系统处理器支持硬件虚拟化&#xff0c;在Linux终端中&#xff0c;使用以下命令&#xff1a; egrep -c (vmx|svm) /proc/cpuinfo2、安装KVM及其工具 yum update yum install qemu-kvm libvirt libvirt-python libguestfs-tools vi…

10-菜刀连接木马

找到了漏洞后&#xff0c;并且上传了木马之后才能使用的两款工具 中国菜刀和冰蝎 想办法获取别人的cookie&#xff0c;cookie中有session-id 一、中国菜刀 1、必须提前已经完成木马植入然后才能使用 2、木马必须是POST请求&#xff0c;参数自定义&#xff0c;在菜刀里给出…

机器学习与深度学习 --李宏毅(笔记与个人理解)Day 20

Day 20 RNN 2 实际使用和其他应用 在实际的学习&#xff08;training&#xff09;过程中是如何工作的&#xff1f; step 1 Loss step 2 training Graindent Descent 反向传播的进阶版 – BPTT CLIpping 设置阈值~ 笑死昨天刚看完关伟说的有这玩意的就不是好东西 Why&#xff1…

C语言趣味代码(一)

C语言相关知识点的博客和大家分享完了&#xff0c;接下来我想开始数据结构相关的博客&#xff0c;在此之前呢&#xff0c;有的小伙伴问过我学完C语言的相关知识&#xff0c;我能干些什么呢&#xff1f;只有刷题吗&#xff1f;这不禁让我反思&#xff1a;在我们学习的过程中&…

远程DCS监控

在数字化、智能化的浪潮中&#xff0c;工业控制系统正迎来前所未有的变革。分布式控制系统&#xff08;DCS&#xff09;作为工业自动化领域的核心&#xff0c;其稳定运行对于企业的生产效率和安全至关重要。而远程DCS监控作为实现工业自动化、智能化管理的关键一环&#xff0c;…

【wpf】ObservableCollection 跨线程报错问题

背景 ObservableCollection 我们之前介绍过他和List的区别。ObservableCollection 的好处在于&#xff0c;当集合发生变化时&#xff0c;能发送通知通知界面发生相应的更改。但是ObservableCollection 有个弊端。无法在非UI线程中访问。 要么就是通知失效了&#xff0c;要么就…

MySQL修改数据表的结构

创建数据库 -- create database 创建的数据库名; create database test; 这里创建了一个名为 test 的数据库 选择需要使用的数据库 -- use 数据库名; use test; 这里使用 test 数据库 创建数据表 -- create table 表名(字段名1 数据类型(长度) 约束,字段名2 数据类型(长…

【强化学习的数学原理-赵世钰】课程笔记(九)策略梯度方法(Policy Gradient Method)

目录 一.policy gradient 的基本思路&#xff08;Basic idea of policy gradient&#xff09; 二.定义最优策略的 metrics&#xff0c;也就是 objective function 是什么 三.objective function 的 gradient 四.梯度上升算法&#xff08;REINFORCE&#xff09; 五.总结 上…

企业常用命令(touch/别名/重定向/Linux字符)7368字详谈

企业高薪思维&#xff1a; 企业&#xff08;工作/学习中&#xff09;操作前备份&#xff0c;操作后检查 最小化原则 1.安装软件最小化 2.参数选项最小化 3.登录用户权限最小化&#xff08;不用root登录&#xff09; 要想成功/学习上/工作上 永远比别人多做一点点&#xff08;别…

幻兽帕鲁老板公开发声:腾讯正在制作幻兽帕鲁克隆版

昨天&#xff0c;Pocketpair的老板出来指责中国游戏公司抄袭了他们的游戏Palworld&#xff0c;说这简直是太不可思议了。 Pocketpair的CEO Takuro Mizobe发布了一个叫Auroria的游戏的截图&#xff0c;然后说&#xff1a;“腾讯正在制作Palworld的克隆游戏&#xff01;在中国&a…

Python根据主播直播时间段判定订单销售额归属

写在前面&#xff1a;最近在群里看到一个这样的直播电商的场景觉得还是挺有趣的&#xff0c;于是就想用Python来实现。 需求描述&#xff1a;根据主播直播时间段结合销售订单的付款时间判断所属销售的归属 生成主播在线直播时间段数据 from datetime import datetime, time…

zabbix监控配置(添加主机、主机组和添加监控项等)

zabbix监控配置 文章目录 zabbix监控配置1.添加主机组2.添加主机&#xff08;linux&#xff09;3.添加主机&#xff08;windows&#xff09;4.监控项配置&#xff08;通过模板添加&#xff09;5.监控项配置&#xff08;手动添加&#xff09; 1.添加主机组 2.添加主机&#xff0…

学习Rust的第5天:控制流

Control flow, as the name suggests controls the flow of the program, based on a condition. 控制流&#xff0c;顾名思义&#xff0c;根据条件控制程序的流。 If expression If表达式 An if expression is used when you want to execute a block of code if a condition …

自定义vue-cli 实现预设模板项目

模板结构 主要包括四个部分&#xff1a; preset.jsonprompts.jsgenerator/index.jstemplate/ 项目最终结构 preset.json preset.json 中是一个包含创建新项目所需预定义选项和插件的 JSON 对象&#xff0c;让用户无需在命令提示中选择它们&#xff0c;简称预设&#xff1b;…

openGauss学习笔记-265 openGauss性能调优-TPCC性能调优测试指导-操作系统配置

文章目录 openGauss学习笔记-265 openGauss性能调优-TPCC性能调优测试指导-操作系统配置265.1安装openEuler操作系统265.2 修改操作系统内核PAGESIZE为64KB。265.3 关闭CPU中断的服务irqbalance openGauss学习笔记-265 openGauss性能调优-TPCC性能调优测试指导-操作系统配置 本…

2011年认证杯SPSSPRO杯数学建模C题(第二阶段)你的爱车入保险了吗全过程文档及程序

2011年认证杯SPSSPRO杯数学建模 C题 你的爱车入保险了吗 原题再现&#xff1a; 近几年&#xff0c;国内汽车销售市场异常火爆&#xff0c;销售量屡创新高。车轮上的世界&#xff0c;保险已经与我们如影随形。汽车保险&#xff0c;简称车险&#xff0c;是指对机动车辆由于自然…

计算机考研都将采用408!?

这个根本不可能&#xff0c;高考还没做到全国统一考试呢 每个学校对于计算机招生的需求是不一样的&#xff0c;比如清华大学&#xff0c;专业课912&#xff0c;算的上是最难的计算机专业课了&#xff0c;那他为什么搞这么难啊&#xff0c;还不是因为那群敢考清华的卷王们太变态…