欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot實現(xiàn)滑塊驗證碼驗證登陸校驗功能詳解

 更新時間:2022年09月24日 10:04:39   作者:Java升級之路  
驗證碼作為一種自然人的機器人的判別工具,被廣泛的用于各種防止程序做自動化的場景中。傳統(tǒng)的字符型驗證安全性已經(jīng)名存實亡的情況下,各種新型的驗證碼如雨后春筍般涌現(xiàn),今天給大家分享一篇SpringBoot實現(xiàn)滑塊驗證碼

前言

驗證碼一直是各類網(wǎng)站登錄和注冊的一種校驗方式,是用來防止有人惡意使用腳本批量進行操作從而設(shè)置的一種安全保護方式。隨著近幾年技術(shù)的發(fā)展,人們對于系統(tǒng)安全性和用戶體驗的要求越來越高,大多數(shù)網(wǎng)站系統(tǒng)都逐漸采用行為驗證碼來代替?zhèn)鹘y(tǒng)的圖片驗證碼。

今天這篇文章就來記錄一下,我是如何實現(xiàn)從前端、到后端校驗的整個流程的。

一、實現(xiàn)效果

無圖無真相,實現(xiàn)的效果如下圖所示,點擊登錄后彈出一個彈出層,拼圖是由后端生成的,拖動滑塊位置,后端校驗是否已拖動到指定的位置。

二、實現(xiàn)思路

整體的實現(xiàn)思路如下:

1、從服務(wù)器隨機取一張底透明有形狀的模板圖,再隨機取一張背景圖、

2、隨機生成摳圖塊坐標

3、根據(jù)步驟2的坐標點,對背景大圖的摳圖區(qū)域的顏色進行處理,新建的圖像根據(jù)輪廓圖顏色賦值,背景圖生成遮罩層。

4、完成以上步驟之后得到兩張圖(扣下來的方塊圖,帶有摳圖區(qū)域陰影的原圖),將這兩張圖和摳圖區(qū)域的y坐標傳到前臺,x坐標存入redis。

5、前端在移動拼圖時將滑動距離x坐標參數(shù)請求后臺驗證,服務(wù)器根據(jù)redis取出x坐標與參數(shù)的x進行比較,如果在伐值內(nèi)則驗證通過。如果滑動不成功,自動刷新圖片,重置拼圖,滑動成功,且賬號密碼正確就直接跳轉(zhuǎn)到首頁。

三、實現(xiàn)步驟

1. 后端 java 代碼

1.1 新建一個拼圖驗證碼類

代碼如下(示例):

@Data
public class Captcha {
    /**
     * 隨機字符串
     **/
    private String nonceStr;
    /**
     * 驗證值
     **/
    private String value;
    /**
     * 生成的畫布的base64
     **/
    private String canvasSrc;
    /**
     * 畫布寬度
     **/
    private Integer canvasWidth;
    /**
     * 畫布高度
     **/
    private Integer canvasHeight;
    /**
     * 生成的阻塞塊的base64
     **/
    private String blockSrc;
    /**
     * 阻塞塊寬度
     **/
    private Integer blockWidth;
    /**
     * 阻塞塊高度
     **/
    private Integer blockHeight;
    /**
     * 阻塞塊凸凹半徑
     **/
    private Integer blockRadius;
    /**
     * 阻塞塊的橫軸坐標
     **/
    private Integer blockX;
    /**
     * 阻塞塊的縱軸坐標
     **/
    private Integer blockY;
    /**
     * 圖片獲取位置
     **/
    private Integer place;
}

1.2 新建一個拼圖驗證碼工具類

代碼如下(示例):

public class CaptchaUtils {
    /**
     * 網(wǎng)絡(luò)圖片地址
     **/
    private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg";
    /**
     * 本地圖片地址
     **/
    private final static String IMG_PATH = "E:/Temp/wallpaper/%s.jpg";
    /**
     * 入?yún)⑿r炘O(shè)置默認值
     **/
    public static void checkCaptcha(Captcha captcha) {
        //設(shè)置畫布寬度默認值
        if (captcha.getCanvasWidth() == null) {
            captcha.setCanvasWidth(320);
        }
        //設(shè)置畫布高度默認值
        if (captcha.getCanvasHeight() == null) {
            captcha.setCanvasHeight(155);
        }
        //設(shè)置阻塞塊寬度默認值
        if (captcha.getBlockWidth() == null) {
            captcha.setBlockWidth(65);
        }
        //設(shè)置阻塞塊高度默認值
        if (captcha.getBlockHeight() == null) {
            captcha.setBlockHeight(55);
        }
        //設(shè)置阻塞塊凹凸半徑默認值
        if (captcha.getBlockRadius() == null) {
            captcha.setBlockRadius(9);
        }
        //設(shè)置圖片來源默認值
        if (captcha.getPlace() == null) {
            captcha.setPlace(0);
        }
    }
    /**
     * 獲取指定范圍內(nèi)的隨機數(shù)
     **/
    public static int getNonceByRange(int start, int end) {
        Random random = new Random();
        return random.nextInt(end - start + 1) + start;
    }
    /**
     * 獲取驗證碼資源圖
     **/
    public static BufferedImage getBufferedImage(Integer place) {
        try {
            //隨機圖片
            int nonce = getNonceByRange(0, 1000);
            //獲取網(wǎng)絡(luò)資源圖片
            if (0 == place) {
                String imgUrl = String.format(IMG_URL, nonce);
                URL url = new URL(imgUrl);
                return ImageIO.read(url.openStream());
            }
            //獲取本地圖片
            else {
                String imgPath = String.format(IMG_PATH, nonce);
                File file = new File(imgPath);
                return ImageIO.read(file);
            }
        } catch (Exception e) {
            System.out.println("獲取拼圖資源失敗");
            //異常處理
            return null;
        }
    }
    /**
     * 調(diào)整圖片大小
     **/
    public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) {
        Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics2D = resultImage.createGraphics();
        graphics2D.drawImage(image, 0, 0, null);
        graphics2D.dispose();
        return resultImage;
    }
    /**
     * 摳圖,并生成阻塞塊
     **/
    public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth, int blockHeight, int blockRadius, int blockX, int blockY) {
        BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        //阻塞塊的輪廓圖
        int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius);
        //創(chuàng)建阻塞塊具體形狀
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                try {
                    //原圖中對應(yīng)位置變色處理
                    if (blockData[i][j] == 1) {
                        //背景設(shè)置為黑色
                        waterImage.setRGB(i, j, Color.BLACK.getRGB());
                        blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j));
                        //輪廓設(shè)置為白色,取帶像素和無像素的界點,判斷該點是不是臨界輪廓點
                        if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0 || blockData[i][j - 1] == 0) {
                            blockImage.setRGB(i, j, Color.WHITE.getRGB());
                            waterImage.setRGB(i, j, Color.WHITE.getRGB());
                        }
                    }
                    //這里把背景設(shè)為透明
                    else {
                        blockImage.setRGB(i, j, Color.TRANSLUCENT);
                        waterImage.setRGB(i, j, Color.TRANSLUCENT);
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    //防止數(shù)組下標越界異常
                }
            }
        }
        //在畫布上添加阻塞塊水印
        addBlockWatermark(canvasImage, waterImage, blockX, blockY);
    }
    /**
     * 構(gòu)建拼圖輪廓軌跡
     **/
    private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) {
        int[][] data = new int[blockWidth][blockHeight];
        double po = Math.pow(blockRadius, 2);
        //隨機生成兩個圓的坐標,在4個方向上 隨機找到2個方向添加凸/凹
        //凸/凹1
        int face1 = RandomUtils.nextInt(0,4);
        //凸/凹2
        int face2;
        //保證兩個凸/凹不在同一位置
        do {
            face2 = RandomUtils.nextInt(0,4);
        } while (face1 == face2);
        //獲取凸/凹起位置坐標
        int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius);
        int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius);
        //隨機凸/凹類型
        int shape = getNonceByRange(0, 1);
        //圓的標準方程 (x-a)2+(y-b)2=r2,標識圓心(a,b),半徑為r的圓
        //計算需要的小圖輪廓,用二維數(shù)組來表示,二維數(shù)組有兩張值,0和1,其中0表示沒有顏色,1有顏色
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                data[i][j] = 0;
                //創(chuàng)建中間的方形區(qū)域
                if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius && j <= blockHeight - blockRadius)) {
                    data[i][j] = 1;
                }
                double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2);
                double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2);
                //創(chuàng)建兩個凸/凹
                if (d1 <= po || d2 <= po) {
                    data[i][j] = shape;
                }
            }
        }
        return data;
    }
    /**
     * 根據(jù)朝向獲取圓心坐標
     */
    private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) {
        //上
        if (0 == face) {
            return new int[]{blockWidth / 2 - 1, blockRadius};
        }
        //左
        else if (1 == face) {
            return new int[]{blockRadius, blockHeight / 2 - 1};
        }
        //下
        else if (2 == face) {
            return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1};
        }
        //右
        else if (3 == face) {
            return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1};
        }
        return null;
    }
    /**
     * 在畫布上添加阻塞塊水印
     */
    private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) {
        Graphics2D graphics2D = canvasImage.createGraphics();
        graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f));
        graphics2D.drawImage(blockImage, x, y, null);
        graphics2D.dispose();
    }
    /**
     * BufferedImage轉(zhuǎn)BASE64
     */
    public static String toBase64(BufferedImage bufferedImage, String type) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, type, byteArrayOutputStream);
            String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
            return String.format("data:image/%s;base64,%s", type, base64);
        } catch (IOException e) {
            System.out.println("圖片資源轉(zhuǎn)換BASE64失敗");
            //異常處理
            return null;
        }
    }
}

1.3 新建一個 service 類

代碼如下(示例):

@Service
public class CaptchaService {
    /**
     * 拼圖驗證碼允許偏差
     **/
    private static Integer ALLOW_DEVIATION = 3;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 校驗驗證碼
     * @param imageKey
     * @param imageCode
     * @return boolean
     **/
    public String checkImageCode(String imageKey, String imageCode) {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String text = ops.get("imageCode:" + imageKey);
        if(StrUtil.isBlank(text)){
            return "驗證碼已失效";
        }
        // 根據(jù)移動距離判斷驗證是否成功
        if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) {
            return "驗證失敗,請控制拼圖對齊缺口";
        }
        return null;
    }
    /**
     * 緩存驗證碼,有效期15分鐘
     * @param key
     * @param code
     **/
    public void saveImageCode(String key, String code) {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set("imageCode:" + key, code, 15, TimeUnit.MINUTES);
    }
    /**
     * 獲取驗證碼拼圖(生成的摳圖和帶摳圖陰影的大圖及摳圖坐標)
     **/
    public Object getCaptcha(Captcha captcha) {
        //參數(shù)校驗
        CaptchaUtils.checkCaptcha(captcha);
        //獲取畫布的寬高
        int canvasWidth = captcha.getCanvasWidth();
        int canvasHeight = captcha.getCanvasHeight();
        //獲取阻塞塊的寬高/半徑
        int blockWidth = captcha.getBlockWidth();
        int blockHeight = captcha.getBlockHeight();
        int blockRadius = captcha.getBlockRadius();
        //獲取資源圖
        BufferedImage canvasImage = CaptchaUtils.getBufferedImage(captcha.getPlace());
        //調(diào)整原圖到指定大小
        canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight);
        //隨機生成阻塞塊坐標
        int blockX = CaptchaUtils.getNonceByRange(blockWidth, canvasWidth - blockWidth - 10);
        int blockY = CaptchaUtils.getNonceByRange(10, canvasHeight - blockHeight + 1);
        //阻塞塊
        BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        //新建的圖像根據(jù)輪廓圖顏色賦值,源圖生成遮罩
        CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY);
        // 移動橫坐標
        String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
        // 緩存
        saveImageCode(nonceStr,String.valueOf(blockX));
        //設(shè)置返回參數(shù)
        captcha.setNonceStr(nonceStr);
        captcha.setBlockY(blockY);
        captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png"));
        captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png"));
        return captcha;
    }
}

1.4 新建一個 controller 類

代碼如下(示例):

@RestController
@RequestMapping("/captcha")
public class CaptchaController {
    @Autowired
    private CaptchaService captchaService;
    @ApiOperation(value = "生成驗證碼拼圖")
    @PostMapping("get-captcha")
    public R getCaptcha(@RequestBody Captcha captcha) {
        return R.ok(captchaService.getCaptcha(captcha));
    }
}

1.5 登錄接口

代碼如下(示例):

    @ApiOperation(value = "登錄")
    @PostMapping(value = "login")
    public R login(@RequestBody LoginVo loginVo) {
        // 只有開啟了驗證碼功能才需要驗證
        if (needAuthCode) {
            String msg = captchaService.checkImageCode(loginVo.getNonceStr(),loginVo.getValue());
            if (StringUtils.isNotBlank(msg)) {
                return R.error(msg);
            }
        }
        String token = loginService.login(loginVo.getUserName(), loginVo.getPassWord());
        if (StringUtils.isBlank(token)) {
            return R.error("用戶名或密碼錯誤");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        return R.ok(tokenMap);
    }

2. 前端 vue 代碼

2.1 新建一個 sliderVerify 組件

代碼如下(示例):

<template>
    <div class="slide-verify" :style="{width: canvasWidth + 'px'}" onselectstart="return false;">
        <!-- 圖片加載遮蔽罩 -->
        <div :class="{'img-loading': isLoading}" :style="{height: canvasHeight + 'px'}" v-if="isLoading"/>
        <!-- 認證成功后的文字提示 -->
        <div class="success-hint" :style="{height: canvasHeight + 'px'}" v-if="verifySuccess">{{ successHint }}</div>
        <!--刷新按鈕-->
        <div class="refresh-icon" @click="refresh"/>
        <!--前端生成-->
        <template v-if="isFrontCheck">
            <!--驗證圖片-->
            <canvas ref="canvas" class="slide-canvas" :width="canvasWidth" :height="canvasHeight"/>
            <!--阻塞塊-->
            <canvas ref="block" class="slide-block" :width="canvasWidth" :height="canvasHeight"/>
        </template>
        <!--后端生成-->
        <template v-else>
            <!--驗證圖片-->
            <img ref="canvas" class="slide-canvas" :width="canvasWidth" :height="canvasHeight"/>
            <!--阻塞塊-->
            <img ref="block" :class="['slide-block', {'verify-fail': verifyFail}]"/>
        </template>
        <!-- 滑動條 -->
        <div class="slider" :class="{'verify-active': verifyActive, 'verify-success': verifySuccess, 'verify-fail': verifyFail}">
            <!--滑塊-->
            <div class="slider-box" :style="{width: sliderBoxWidth}">
                <!-- 按鈕 -->
                <div class="slider-button" id="slider-button" :style="{left: sliderButtonLeft}">
                    <!-- 按鈕圖標 -->
                    <div class="slider-button-icon"/>
                </div>
            </div>
            <!--滑動條提示文字-->
            <span class="slider-hint">{{ sliderHint }}</span>
        </div>
    </div>
</template>
<script>
function sum(x, y) {
    return x + y;
}
function square(x) {
    return x * x;
}
import { getCodeImg } from "@/api/login";
export default {
    name: 'sliderVerify',
    props: {
        // 阻塞塊長度
        blockLength: {
            type: Number,
            default: 42,
        },
        // 阻塞塊弧度
        blockRadius: {
            type: Number,
            default: 10,
        },
        // 畫布寬度
        canvasWidth: {
            type: Number,
            default: 320,
        },
        // 畫布高度
        canvasHeight: {
            type: Number,
            default: 155,
        },
        // 滑塊操作提示
        sliderHint: {
            type: String,
            default: '向右滑動',
        },
        // 可允許的誤差范圍?。粸?時,則表示滑塊要與凹槽完全重疊,才能驗證成功。默認值為5,若為 -1 則不進行機器判斷
        accuracy: {
            type: Number,
            default: 3,
        },
        // 圖片資源數(shù)組
        imageList: {
            type: Array,
            default: () => [],
        },
    },
    data() {
        return {
            // 前端校驗
            isFrontCheck: false,
            // 校驗進行狀態(tài)
            verifyActive: false,
            // 校驗成功狀態(tài)
            verifySuccess: false,
            // 校驗失敗狀態(tài)
            verifyFail: false,
            // 阻塞塊對象
            blockObj: null,
            // 圖片畫布對象
            canvasCtx: null,
            // 阻塞塊畫布對象
            blockCtx: null,
            // 阻塞塊寬度
            blockWidth: this.blockLength * 2,
            // 阻塞塊的橫軸坐標
            blockX: undefined,
            // 阻塞塊的縱軸坐標
            blockY: undefined,
            // 圖片對象
            image: undefined,
            // 移動的X軸坐標
            originX: undefined,
            // 移動的Y軸做坐標
            originY: undefined,
            // 拖動距離數(shù)組
            dragDistanceList: [],
            // 滑塊箱拖動寬度
            sliderBoxWidth: 0,
            // 滑塊按鈕距離左側(cè)起點位置
            sliderButtonLeft: 0,
            // 鼠標按下狀態(tài)
            isMouseDown: false,
            // 圖片加載提示,防止圖片沒加載完就開始驗證
            isLoading: true,
            // 時間戳,計算滑動時長
            timestamp: null,
            // 成功提示
            successHint: '',
            // 隨機字符串
            nonceStr: undefined,
        };
    },
    mounted() {
        this.init();
    },
    methods: {
        /* 初始化*/
        init() {
            this.initDom();
            this.bindEvents();
        },
        /* 初始化DOM對象*/
        initDom() {
            this.blockObj = this.$refs.block;
            if (this.isFrontCheck) {
                this.canvasCtx = this.$refs.canvas.getContext('2d');
                this.blockCtx = this.blockObj.getContext('2d');
                this.initImage();
            } else {
                this.getCaptcha();
            }
        },
        /* 后臺獲取驗證碼*/
        getCaptcha() {
            let self = this;
            //取后端默認值
            const data = {};
          getCodeImg(data).then((response) => {
                const data = response.data;
                self.nonceStr = data.nonceStr;
                self.$refs.block.src = data.blockSrc;
                self.$refs.block.style.top = data.blockY + 'px';
                self.$refs.canvas.src = data.canvasSrc;
            }).finally(() => {
                self.isLoading = false;
            });
        },
        /* 前端獲取驗證碼*/
        initImage() {
            const image = this.createImage(() => {
                this.drawBlock();
                let {canvasWidth, canvasHeight, blockX, blockY, blockRadius, blockWidth} = this;
                this.canvasCtx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
                this.blockCtx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
                // 將摳圖防止最左邊位置
                let yAxle = blockY - blockRadius * 2;
                let ImageData = this.blockCtx.getImageData(blockX, yAxle, blockWidth, blockWidth);
                this.blockObj.width = blockWidth;
                this.blockCtx.putImageData(ImageData, 0, yAxle);
                // 圖片加載完關(guān)閉遮蔽罩
                this.isLoading = false;
                // 前端校驗設(shè)置特殊值
                this.nonceStr = 'loyer';
            });
            this.image = image;
        },
        /* 創(chuàng)建image對象*/
        createImage(onload) {
            const image = document.createElement('img');
            image.crossOrigin = 'Anonymous';
            image.onload = onload;
            image.onerror = () => {
                image.src = require('../../assets/images/bgImg.jpg');
            };
            image.src = this.getImageSrc();
            return image;
        },
        /* 獲取imgSrc*/
        getImageSrc() {
            const len = this.imageList.length;
            return len > 0 ? this.imageList[this.getNonceByRange(0, len)] : `https://loyer.wang/view/ftp/wallpaper/${this.getNonceByRange(1, 1000)}.jpg`;
        },
        /* 根據(jù)指定范圍獲取隨機數(shù)*/
        getNonceByRange(start, end) {
            return Math.round(Math.random() * (end - start) + start);
        },
        /* 繪制阻塞塊*/
        drawBlock() {
            this.blockX = this.getNonceByRange(this.blockWidth + 10, this.canvasWidth - (this.blockWidth + 10));
            this.blockY = this.getNonceByRange(10 + this.blockRadius * 2, this.canvasHeight - (this.blockWidth + 10));
            this.draw(this.canvasCtx, 'fill');
            this.draw(this.blockCtx, 'clip');
        },
        /* 繪制事件*/
        draw(ctx, operation) {
            const PI = Math.PI;
            let {blockX: x, blockY: y, blockLength: l, blockRadius: r} = this;
            // 繪制
            ctx.beginPath();
            ctx.moveTo(x, y);
            ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
            ctx.lineTo(x + l, y);
            ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
            ctx.lineTo(x + l, y + l);
            ctx.lineTo(x, y + l);
            ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
            ctx.lineTo(x, y);
            // 修飾
            ctx.lineWidth = 2;
            ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)';
            ctx.stroke();
            ctx[operation]();
            ctx.globalCompositeOperation = 'destination-over';
        },
        /* 事件綁定*/
        bindEvents() {
            // 監(jiān)聽鼠標按下事件
            document.getElementById('slider-button').addEventListener('mousedown', (event) => {
                this.startEvent(event.clientX, event.clientY);
            });
            // 監(jiān)聽鼠標移動事件
            document.addEventListener('mousemove', (event) => {
                this.moveEvent(event.clientX, event.clientY);
            });
            // 監(jiān)聽鼠標離開事件
            document.addEventListener('mouseup', (event) => {
                this.endEvent(event.clientX);
            });
            // 監(jiān)聽觸摸開始事件
            document.getElementById('slider-button').addEventListener('touchstart', (event) => {
                this.startEvent(event.changedTouches[0].pageX, event.changedTouches[0].pageY);
            });
            // 監(jiān)聽觸摸滑動事件
            document.addEventListener('touchmove', (event) => {
                this.moveEvent(event.changedTouches[0].pageX, event.changedTouches[0].pageY);
            });
            // 監(jiān)聽觸摸離開事件
            document.addEventListener('touchend', (event) => {
                this.endEvent(event.changedTouches[0].pageX);
            });
        },
        /* 校驗圖片是否存在*/
        checkImgSrc() {
            if (this.isFrontCheck) {
                return true;
            }
            return !!this.$refs.canvas.src;
        },
        /* 滑動開始事件*/
        startEvent(originX, originY) {
            if (!this.checkImgSrc() || this.isLoading || this.verifySuccess) {
                return;
            }
            this.originX = originX;
            this.originY = originY;
            this.isMouseDown = true;
            this.timestamp = +new Date();
        },
        /* 滑動事件*/
        moveEvent(originX, originY) {
            if (!this.isMouseDown) {
                return false;
            }
            const moveX = originX - this.originX;
            const moveY = originY - this.originY;
            if (moveX < 0 || moveX + 40 >= this.canvasWidth) {
                return false;
            }
            this.sliderButtonLeft = moveX + 'px';
            let blockLeft = (this.canvasWidth - 40 - 20) / (this.canvasWidth - 40) * moveX;
            this.blockObj.style.left = blockLeft + 'px';
            this.verifyActive = true;
            this.sliderBoxWidth = moveX + 'px';
            this.dragDistanceList.push(moveY);
        },
        /* 滑動結(jié)束事件*/
        endEvent(originX) {
            if (!this.isMouseDown) {
                return false;
            }
            this.isMouseDown = false;
            if (originX === this.originX) {
                return false;
            }
            // 開始校驗
            this.isLoading = true;
            // 校驗結(jié)束
            this.verifyActive = false;
            // 滑動時長
            this.timestamp = +new Date() - this.timestamp;
            // 移動距離
            const moveLength = parseInt(this.blockObj.style.left);
            // 限制操作時長10S,超出判斷失敗
            if (this.timestamp > 10000) {
                this.verifyFailEvent();
            } else
                    // 人為操作判定
            if (!this.turingTest()) {
                this.verifyFail = true;
                this.$emit('again');
            } else
                    // 是否前端校驗
            if (this.isFrontCheck) {
                const accuracy = this.accuracy <= 1 ? 1 : this.accuracy > 10 ? 10 : this.accuracy; // 容錯精度值
                const spliced = Math.abs(moveLength - this.blockX) <= accuracy; // 判斷是否重合
                if (!spliced) {
                    this.verifyFailEvent();
                } else {
                    // 設(shè)置特殊值,后臺特殊處理,直接驗證通過
                    this.$emit('success', {nonceStr: this.nonceStr, value: moveLength});
                }
            } else {
                this.$emit('success', {nonceStr: this.nonceStr, value: moveLength});
            }
        },
        /* 圖靈測試*/
        turingTest() {
            const arr = this.dragDistanceList; // 拖動距離數(shù)組
            const average = arr.reduce(sum) / arr.length; // 平均值
            const deviations = arr.map((x) => x - average); // 偏離值
            const stdDev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length); // 標準偏差
            return average !== stdDev; // 判斷是否人為操作
        },
        /* 校驗成功*/
        verifySuccessEvent() {
            this.isLoading = false;
            this.verifySuccess = true;
            const elapsedTime = (this.timestamp / 1000).toFixed(1);
            if (elapsedTime < 1) {
                this.successHint = `僅僅${elapsedTime}S,你的速度快如閃電`;
            } else if (elapsedTime < 2) {
                this.successHint = `只用了${elapsedTime}S,這速度簡直完美`;
            } else {
                this.successHint = `耗時${elapsedTime}S,爭取下次再快一點`;
            }
        },
        /* 校驗失敗*/
        verifyFailEvent(msg) {
            this.verifyFail = true;
            this.$emit('fail', msg);
            this.refresh();
        },
        /* 刷新圖片驗證碼*/
        refresh() {
            // 延遲class的刪除,等待動畫結(jié)束
            setTimeout(() => {
                this.verifyFail = false;
            }, 500);
            this.isLoading = true;
            this.verifyActive = false;
            this.verifySuccess = false;
            this.blockObj.style.left = 0;
            this.sliderBoxWidth = 0;
            this.sliderButtonLeft = 0;
            if (this.isFrontCheck) {
                // 刷新畫布
                let {canvasWidth, canvasHeight} = this;
                this.canvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
                this.blockCtx.clearRect(0, 0, canvasWidth, canvasHeight);
                this.blockObj.width = canvasWidth;
                // 刷新圖片
                this.image.src = this.getImageSrc();
            } else {
                this.getCaptcha();
            }
        },
    },
};
</script>
<style scoped>
    .slide-verify {
        position: relative;
    }
    /*圖片加載樣式*/
    .img-loading {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        bottom: 0;
        z-index: 999;
        animation: loading 1.5s infinite;
        background-image: url(../../assets/images/loading.svg);
        background-repeat: no-repeat;
        background-position: center center;
        background-size: 100px;
        background-color: #737c8e;
        border-radius: 5px;
    }
    @keyframes loading {
        0% {
            opacity: .7;
        }
        100% {
            opacity: 9;
        }
    }
    /*認證成功后的文字提示*/
    .success-hint {
        position: absolute;
        top: 0;
        right: 0;
        left: 0;
        z-index: 999;
        display: flex;
        align-items: center;
        justify-content: center;
        background: rgba(255, 255, 255, 0.8);
        color: #2CD000;
        font-size: large;
    }
    /*刷新按鈕*/
    .refresh-icon {
        position: absolute;
        right: 0;
        top: 0;
        width: 35px;
        height: 35px;
        cursor: pointer;
        background: url("../../assets/images/light.png") 0 -432px;
        background-size: 35px 470px;
    }
    /*驗證圖片*/
    .slide-canvas {
        border-radius: 5px;
    }
    /*阻塞塊*/
    .slide-block {
        position: absolute;
        left: 0;
        top: 0;
    }
    /*校驗失敗時的阻塞塊樣式*/
    .slide-block.verify-fail {
        transition: left 0.5s linear;
    }
    /*滑動條*/
    .slider {
        position: relative;
        text-align: center;
        width: 100%;
        height: 40px;
        line-height: 40px;
        margin-top: 15px;
        background: #f7f9fa;
        color: #45494c;
        border: 1px solid #e4e7eb;
        border-radius: 5px;
    }
    /*滑動盒子*/
    .slider-box {
        position: absolute;
        left: 0;
        top: 0;
        height: 40px;
        border: 0 solid #1991FA;
        background: #D1E9FE;
        border-radius: 5px;
    }
    /*滑動按鈕*/
    .slider-button {
        position: absolute;
        top: 0;
        left: 0;
        width: 40px;
        height: 40px;
        background: #fff;
        box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
        cursor: pointer;
        transition: background .2s linear;
        border-radius: 5px;
    }
    /*鼠標懸浮時的按鈕樣式*/
    .slider-button:hover {
        background: #1991FA
    }
    /*鼠標懸浮時的按鈕圖標樣式*/
    .slider-button:hover .slider-button-icon {
        background-position: 0 -13px
    }
    /*滑動按鈕圖標*/
    .slider-button-icon {
        position: absolute;
        top: 15px;
        left: 13px;
        width: 15px;
        height: 13px;
        background: url("../../assets/images/light.png") 0 -26px;
        background-size: 35px 470px
    }
    /*校驗時的按鈕樣式*/
    .verify-active .slider-button {
        height: 38px;
        top: -1px;
        border: 1px solid #1991FA;
    }
    /*校驗時的滑動箱樣式*/
    .verify-active .slider-box {
        height: 38px;
        border-width: 1px;
    }
    /*校驗成功時的滑動箱樣式*/
    .verify-success .slider-box {
        height: 38px;
        border: 1px solid #52CCBA;
        background-color: #D2F4EF;
    }
    /*校驗成功時的按鈕樣式*/
    .verify-success .slider-button {
        height: 38px;
        top: -1px;
        border: 1px solid #52CCBA;
        background-color: #52CCBA !important;
    }
    /*校驗成功時的按鈕圖標樣式*/
    .verify-success .slider-button-icon {
        background-position: 0 0 !important;
    }
    /*校驗失敗時的滑動箱樣式*/
    .verify-fail .slider-box {
        height: 38px;
        border: 1px solid #f57a7a;
        background-color: #fce1e1;
        transition: width 0.5s linear;
    }
    /*校驗失敗時的按鈕樣式*/
    .verify-fail .slider-button {
        height: 38px;
        top: -1px;
        border: 1px solid #f57a7a;
        background-color: #f57a7a !important;
        transition: left 0.5s linear;
    }
    /*校驗失敗時的按鈕圖標樣式*/
    .verify-fail .slider-button-icon {
        top: 14px;
        background-position: 0 -82px !important;
    }
    /*校驗狀態(tài)下的提示文字隱藏*/
    .verify-active .slider-hint,
    .verify-success .slider-hint,
    .verify-fail .slider-hint {
        display: none;
    }
</style>

2.2 在登錄頁使用滑塊組件

代碼如下(示例):

      <template>
  <div class="login-container">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
      ...省略無關(guān)代碼
      <!--滑塊驗證-->
      <el-dialog title="請拖動滑塊完成拼圖" width="360px" :visible.sync="isShowSliderVerify" :close-on-click-modal="false" @closed="refresh" append-to-body>
        <slider-verify ref="sliderVerify" @success="onSuccess" @fail="onFail" @again="onAgain"/>
      </el-dialog>
      <el-button :loading="loading" round style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登錄</el-button>
    </el-form>
  </div>
</template>
<script>
  import sliderVerify from './sliderVerify';
export default {
  name: 'Login',
  components: {
    sliderVerify,
  },
  data() {
    return {
      loginForm: {
        userName: '',
        passWord: '',
        // 隨機字符串
        nonceStr: '',
        // 驗證值
        value: '',
      },
      loading: false,
      // 是否顯示滑塊驗證
      isShowSliderVerify: false,
    }
  },
  methods: {
    /* 提交*/
    handleLogin() {
      let self = this;
      self.$refs.loginForm.validate((flag) => {
        self.isShowSliderVerify = flag;
      });
    },
    /* 登錄*/
    login() {
      let self = this;
      self.loading = true;
      self.$store.dispatch('user/login', self.loginForm).then(() => {
          self.$refs.sliderVerify.verifySuccessEvent();
          setTimeout(() => {
            self.isShowSliderVerify = false;
            self.message('success', '登錄成功');
          }, 500);
          this.$router.push({ path: this.redirect || '/' })
      }).catch(() => {
        self.$refs.sliderVerify.verifyFailEvent();
      });
    },
    /* 滑動驗證成功*/
    onSuccess(captcha) {
      Object.assign(this.loginForm, captcha);
      this.login();
    },
    /* 滑動驗證失敗*/
    onFail(msg) {
      //this.message('error', msg || '驗證失敗,請控制拼圖對齊缺口');
    },
    /* 滑動驗證異常*/
    onAgain() {
      this.message('error', '滑動操作異常,請重試');
    },
    /* 刷新驗證碼*/
    refresh() {
      this.$refs.sliderVerify.refresh();
    },
    /* 提示彈框*/
    message(type, message) {
      this.$message({
        showClose: true,
        type: type,
        message: message,
        duration: 1500,
      });
    },
  },
}
</script>

到此這篇關(guān)于SpringBoot實現(xiàn)滑塊驗證碼驗證登陸校驗功能詳解的文章就介紹到這了,更多相關(guān)SpringBoot滑塊驗證碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 整合模板徹底解決ssm配置難題

    Java 整合模板徹底解決ssm配置難題

    SSM框架是spring MVC ,spring和mybatis框架的整合,是標準的MVC模式,將整個系統(tǒng)劃分為表現(xiàn)層,controller層,service層,DAO層四層,使用spring MVC負責請求的轉(zhuǎn)發(fā)和視圖管理,spring實現(xiàn)業(yè)務(wù)對象管理,mybatis作為數(shù)據(jù)對象的持久化引擎
    2021-10-10
  • 詳解Java中JSON數(shù)據(jù)的生成與解析

    詳解Java中JSON數(shù)據(jù)的生成與解析

    今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Java中JSON數(shù)據(jù)的生成與解析展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • 使用apache 的FileUtils處理文件的復制等操作方式

    使用apache 的FileUtils處理文件的復制等操作方式

    這篇文章主要介紹了使用apache 的FileUtils處理文件的復制等操作方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring Boot面試題總結(jié)

    Spring Boot面試題總結(jié)

    這篇文章主要介紹了Spring Boot面試題總結(jié),本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • Java中實現(xiàn)List分隔成子List詳解

    Java中實現(xiàn)List分隔成子List詳解

    大家好,本篇文章主要講的是Java中實現(xiàn)List分隔成子List詳解,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • 什么情況下會出現(xiàn)java.io.IOException?:?Broken?pipe這個錯誤以及解決辦法

    什么情況下會出現(xiàn)java.io.IOException?:?Broken?pipe這個錯誤以及解決辦法

    這篇文章主要介紹了什么情況下會出現(xiàn)java.io.IOException?:?Broken?pipe這個錯誤以及解決辦法的相關(guān)資料,這個錯誤表示通信另一端已關(guān)閉連接,常發(fā)生在客戶端關(guān)閉連接、網(wǎng)絡(luò)超時或資源不足等情況,文中將解決辦法介紹的非常詳細,需要的朋友可以參考下
    2024-10-10
  • Spring BeanPostProcessor(后置處理器)的用法

    Spring BeanPostProcessor(后置處理器)的用法

    這篇文章主要介紹了Spring BeanPostProcessor(后置處理器)的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 使用SpringJPA?直接實現(xiàn)count(*)

    使用SpringJPA?直接實現(xiàn)count(*)

    這篇文章主要介紹了SpringJPA?直接實現(xiàn)count(*),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java圖片與二進制相互轉(zhuǎn)換實現(xiàn)示例講解

    Java圖片與二進制相互轉(zhuǎn)換實現(xiàn)示例講解

    這篇文章主要介紹了Java圖片與二進制相互轉(zhuǎn)換實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2023-03-03
  • 教你怎么通過IDEA設(shè)置堆內(nèi)存空間

    教你怎么通過IDEA設(shè)置堆內(nèi)存空間

    這篇文章主要介紹了教你怎么通過IDEA設(shè)置堆內(nèi)存空間,文中有非常詳細的代碼示例,對正在使用IDEA的小伙伴們很有幫助喲,需要的朋友可以參考下
    2021-05-05

最新評論