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,clickWord
    type: default
    # 底图路径,支持全路径、项目资源路径
    jigsaw: classpath:images/jigsaw
    # 滑动图路径,支持全路径、项目资源路径
    pic-click: classpath:images/pic-click
    # 缓存类型:local,redis,other
    cache-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
    # 验证失败后,锁定时间间隔,s
    req-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 {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void set(String key, String value, long expiresInSeconds) {
        stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
    }

    @Override
    public boolean exists(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    @Override
    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }

    @Override
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    @Override
    public Long increment(String key, long val) {
        return stringRedisTemplate.opsForValue().increment(key, val);
    }

    @Override
    public 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 {

    @Autowired
    private 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>

    <Verify
      ref="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.which
      if (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.code
        if (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 {

    @Override
    public 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/getpost
核对验证码/captcha/checkpost

接口调用流程
在这里插入图片描述

获取验证码接口:/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呗!!!

  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是使用Spring BootVue.js实现图片验证码登录验证的前后端代码: ## 后端代码(Spring Boot) ### 1. 引入依赖 在`pom.xml`中添加以下依赖: ```xml <dependency> <groupId>com.github.axet</groupId> <artifactId>kaptcha</artifactId> <version>0.0.9</version> </dependency> ``` ### 2. 创建验证码生成器 在`config`包下创建一个`KaptchaConfig`类,用于配置验证码生成器: ```java @Configuration public class KaptchaConfig { @Bean public Producer captchaProducer() { Properties properties = new Properties(); // 配置验证码生成器 // ... return new DefaultKaptcha(); } } ``` ### 3. 创建验证码接口 在`controller`包下创建一个`CaptchaController`类,用于生成验证码图片: ```java @RestController @RequestMapping("/captcha") public class CaptchaController { @Autowired private Producer captchaProducer; @GetMapping("/image") public void captchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception { // 设置响应头信息,告诉浏览器返回的是图片 response.setContentType("image/jpeg"); // 配置验证码生成器 // ... // 生成验证码文本和图片 String captchaText = captchaProducer.createText(); BufferedImage captchaImage = captchaProducer.createImage(captchaText); // 将验证码文本存入session HttpSession session = request.getSession(); session.setAttribute("captchaText", captchaText); // 将验证码图片输出到响应流中 ServletOutputStream out = response.getOutputStream(); ImageIO.write(captchaImage, "jpeg", out); out.flush(); out.close(); } } ``` ### 4. 创建登录接口 在`controller`包下创建一个`LoginController`类,用于处理登录请求: ```java @RestController @RequestMapping("/login") public class LoginController { @PostMapping("/check") public boolean check(@RequestParam String captcha, HttpSession session) { // 获取session中存储的验证码文本 String captchaText = (String) session.getAttribute("captchaText"); // 比较用户输入的验证码和session中存储的验证码是否一致 return captchaText != null && captchaText.equalsIgnoreCase(captcha); } } ``` ## 前端代码(Vue.js) ### 1. 安装依赖 在项目目录下执行以下命令安装依赖: ```bash npm install axios vue-axios vue-qriously ``` ### 2. 创建组件 在`components`目录下创建一个`CaptchaLogin`组件,包含一个输入框、一个验证码图片和一个登录按钮: ```html <template> <div> <input type="text" v-model="captcha" placeholder="请输入验证码" /> <qriously :value="captchaImageUrl"></qriously> <button @click="login">登录</button> </div> </template> <script> import axios from "axios"; import VueAxios from "vue-axios"; import Qriously from "vue-qriously"; export default { name: "CaptchaLogin", components: { Qriously, }, data() { return { captcha: "", captchaImageUrl: "", }; }, created() { this.refreshCaptcha(); }, methods: { refreshCaptcha() { const captchaUrl = `/captcha/image?timestamp=${new Date().getTime()}`; this.captchaImageUrl = captchaUrl; }, login() { axios .post("/login/check", { captcha: this.captcha }) .then((response) => { if (response.data) { alert("登录成功"); } else { alert("验证码错误"); } this.refreshCaptcha(); }); }, }, mounted() { Vue.use(VueAxios, axios); }, }; </script> ``` ### 3. 在页面中使用组件 在需要登录验证的页面中使用`CaptchaLogin`组件: ```html <template> <div> <CaptchaLogin /> </div> </template> <script> import CaptchaLogin from "@/components/CaptchaLogin.vue"; export default { name: "LoginPage", components: { CaptchaLogin, }, }; </script> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PG_强哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值