百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

5 分钟快速上手图形验证码,防止接口被恶意刷量!

liuian 2025-03-29 19:30 18 浏览

5 分钟快速上手图形验证码,防止接口被恶意刷量!

大家好,我是程序员小白条,今天来给大家介绍一个快速实现图形验证码的优秀框架 AJ-Captcha。

需求分析

如果注册接口没有验证码这种类型的限制,很容易会被刷量,因此,一般都会使用邮箱验证码或者图形验证码进行限制,防止被恶意刷接口。邮箱验证码比较容易的是 QQ 验证码,直接配合 SpringMail 即可实现,本文主要实现图形验证码。

文字验证如下

滑动验证如下

后端

1)pom.xml 引入官方依赖包



    com.anji-plus
    spring-boot-starter-captcha
    1.3.0

2)设置配置文件 properties 或者 yml 格式

# 滑动验证,底图路径,不配置将使用默认图片
# 支持全路径
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/jigsaw
aj:
  captcha:
#    jigsaw: "classpath:images/jigsaw"
    #滑动验证,底图路径,不配置将使用默认图片
    ##支持全路径
    # 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-click
#    pic-click: "classpath:images/pic-click"

    # 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis或者memcache,
    # 参考CaptchaCacheServiceRedisImpl.java
    # 如果应用是单点的,也没有使用redis,那默认使用内存。
    # 内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息不同步,导致失败。
    # !!! 注意啦,如果应用有使用spring-boot-starter-data-redis,
    # 请打开CaptchaCacheServiceRedisImpl.java注释。
    # redis ----->  SPI: 在resources目录新建META-INF.services文件夹(两层),参考当前服务resources。
    # 缓存local/redis...
    cache-type: local
    # local缓存的阈值,达到这个值,清除缓存
    #cache-number: 1000
    # local定时清除过期缓存(单位秒),设置为0代表不执行
    #timing-clear: 180
    #spring.redis.host: 10.108.11.46
    #spring.redis.port: 6379
    #spring.redis.password:
    #spring.redis.database: 2
    #spring.redis.timeout: 6000

    # 验证码类型default两种都实例化。
    type: default
    # 汉字统一使用Unicode,保证程序通过@value读取到是中文,可通过这个在线转换;yml格式不需要转换
    # https://tool.chinaz.com/tools/unicode.aspx 中文转Unicode
    # 右下角水印文字(我的水印)
    water-mark: "\u6211\u7684\u6c34\u5370"
    # 右下角水印字体(不配置时,默认使用文泉驿正黑)
    # 由于宋体等涉及到版权,我们jar中内置了开源字体【文泉驿正黑】
    # 方式一:直接配置OS层的现有的字体名称,比如:宋体
    # 方式二:自定义特定字体,请将字体放到工程resources下fonts文件夹,支持ttf\ttc\otf字体
    #water-font: WenQuanZhengHei.ttf
    # 点选文字验证码的文字字体(文泉驿正黑)
    #font-type: WenQuanZhengHei.ttf
    # 校验滑动拼图允许误差偏移量(默认5像素)
    slip-offset: 5
    # aes加密坐标开启或者禁用(true|false)
    aes-status: true
    # 滑动干扰项(0/1/2)
    interference-options: 2

    #点选字体样式 默认Font.BOLD
    font-style: 1
    #点选字体字体大小
    font-size: 25
    #点选文字个数,存在问题,暂不支持修改
    #click-word-count: 4

    history-data-clear-enable: false

    # 接口请求次数一分钟限制是否开启 true|false
    req-frequency-limit-enable: false
    # 验证失败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)创建一个配置类,让 SpringBoot 启动时,扫描到即可

@Configuration
public class CaptchaConfig {



    @Bean(name = "CaptchaCacheService")
    @Primary
    public CaptchaCacheService captchaCacheService(AjCaptchaProperties config){
        //缓存类型redis/local/....
        CaptchaCacheService ret = CaptchaServiceFactory.getCache(config.getCacheType().name());
        return ret;
    }
}

4)创建默认实现类,跟 application.yml 的配置有关

public class DefaultCaptchaServiceImpl extends AbstractCaptchaService {
    DefaultCaptchaServiceImpl() {
        //document why this constructor is empty
    }

    //这个需要实现,如果返回 redis 那就是使用 redis 那套
    public String captchaType() {return "default";}
    @Override
    public void init(Properties config) {
        for (String s : CaptchaServiceFactory.instances.keySet()) {
            if (!this.captchaType().equals(s)) {
                this.getService(s).init(config);
            }
        }
    }
    @Override
    public void destroy(Properties config) {
        for (String s : CaptchaServiceFactory.instances.keySet()) {
            if (!this.captchaType().equals(s)) {
                this.getService(s).destroy(config);
            }
        }
    }
    private CaptchaService getService(String captchaType) {return CaptchaServiceFactory.instances.get(captchaType);}
    @Override
    public ResponseModel get(CaptchaVO captchaVO) {
        if (captchaVO == null) {
            return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
        } else {
            return StringUtils.isEmpty(captchaVO.getCaptchaType()) ? RepCodeEnum.NULL_ERROR.parseError("类型") : this.getService(captchaVO.getCaptchaType()).get(captchaVO);
        }
    }

    @Override
    public ResponseModel check(CaptchaVO captchaVO) {
        if (captchaVO == null) {
            return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
        } else if (StringUtils.isEmpty(captchaVO.getCaptchaType())) {
            return RepCodeEnum.NULL_ERROR.parseError("类型");
        } else {
            return StringUtils.isEmpty(captchaVO.getToken()) ? RepCodeEnum.NULL_ERROR.parseError("token") : this.getService(captchaVO.getCaptchaType()).check(captchaVO);
        }
    }
    @Override
    public ResponseModel verification(CaptchaVO captchaVO) {
        if (captchaVO == null) {
            return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
        } else if (StringUtils.isEmpty(captchaVO.getCaptchaVerification())) {
            return RepCodeEnum.NULL_ERROR.parseError("二次校验参数");
        } else {
            try {
                String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
                if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
                    return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
                }

                CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
            } catch (Exception var3) {
                this.logger.error("验证码坐标解析失败", var3);
                return ResponseModel.errorMsg(var3.getMessage());
            }

            return ResponseModel.success();
        }
    }
}

后端接口

获取验证码接口:http://你的项目地址/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
}

核对验证码接口接口:http://:/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
}

5)完成前面四步后,即可测试接口是否成功被调用,可以用 postman 或者 apifox 等测试工具。

6)在用户注册的 dto 实体类加入新字段 captchaVerification。

@Data
public class UserRegisterRequest implements Serializable {

    private static final long serialVersionUID = 3191241716373120793L;

    private String userAccount;

    private String userPassword;

    private String checkPassword;

    private String captchaVerification;

}

7)在注册接口,加上检验图形验证码的服务。

先自动注入依赖

@Resource
private CaptchaService captchaService;

再加上这段代码即可

        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(userRegisterRequest.getCaptchaVerification());
        ResponseModel response = captchaService.verification(captchaVO);
        if(!response.isSuccess()) {
            //验证码校验失败,返回信息告诉前端
            //repCode  0000  无异常,代表成功
            //repCode  9999  服务器内部异常
            //repCode  0011  参数不能为空
            //repCode  6110  验证码已失效,请重新获取
            //repCode  6111  验证失败
            //repCode  6112  获取验证码失败,请联系管理员
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "验证码错误请重试");
        }

前端

1)引入依赖

npm install aj-captcha-react

2)定义一个函数

 const ref = useRef();
 const click = () => {
    ref.current?.verify();
    console.log(ref.current?.verify());
  };

3)使用组件,这边 valueData 就是注册时带的数据,注意:path 是项目的前缀路径,你的项目可能是 8101 端口,这边是 8204 端口,api 是项目的前缀!

const [valueData, setValueData] = useState();
 {
    const value = valueData;
    if (value) {
        value.captchaVerification = data.captchaVerification;
        await handleSubmit(value);
    }
}}
path="http://localhost:8204/api"
type="auto"
ref={ref}>

完整 tsx 代码如下

const Register: React.FC = () => {
  const ref = useRef();
  const click = () => {
    ref.current?.verify();
    console.log(ref.current?.verify());
  };
  const [type, setType] = useState('register');
  const containerClassName = useEmotionCss(() => {
    return {
      display: 'flex',
      flexDirection: 'column',
      height: '100vh',
      overflow: 'auto',
      backgroundImage:
        "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
      backgroundSize: '100% 100%',
    };
  });
  const [valueData, setValueData] = useState();

  const handleSubmit = async (values: API.UserRegisterRequest) => {
    // 1.先判断两次输入的密码是否一致
    const {userPassword, checkPassword} = values;
    if (userPassword != checkPassword) {
      return message.info("两次输入的密码不一致", 1.5
      )
    }
    // 2.密码一致
   const res =  await userRegister(values);
    if(res.code !== 0){
      return message.error(res.message+"-注册失败,请重试",1.5)
    }
    message.success('注册成功,请登录账号',1.5);
    history.push('/user/login');
  };
  return (
    
{Settings.title}
<LoginForm submitter={{ searchConfig: { submitText: '注册', }, }} contentStyle={{ minWidth: 280, maxWidth: '75vw', }} logo={logo} title="小白条前端模板" subTitle={'快速开发属于自己的前端项目'} initialValues={{ autoLogin: true, }} onFinish={async (values) => { click(); setValueData(values); }} > {type === 'register' && ( <> <ProFormText name="userAccount" fieldProps={{ size: 'large', prefix: , }} placeholder={'请输入账号'} rules={[ { required: true, message: '账号是必填项!', }, { min: 6, type: 'string', message: '长度不能小于 6', }, ]} /> <ProFormText.Password name="userPassword" fieldProps={{ size: 'large', prefix: , }} placeholder={'请输入密码'} rules={[ { required: true, message: '密码是必填项!', }, { min: 8, type: 'string', message: '长度不能小于 8', }, ]} /> <ProFormText.Password name="checkPassword" fieldProps={{ size: 'large', prefix: , }} placeholder="请再次输入密码" rules={[ { required: true, message: '确认密码是必填项!', }, { min: 8, type: 'string', message: '长度不能小于 8', }, ]} /> )} { const value = valueData; if (value) { value.captchaVerification = data.captchaVerification; await handleSubmit(value); } }} path="http://localhost:8102/api" type="auto" ref={ref}>
); }; export default Register;

完成之后即可看到效果图,可以自定义水印和图片,具体可以看官方文档。

官方文档地址:
https://ajcaptcha.beliefteam.cn/captcha-doc/

我的 GitHub 地址:https://github.com/luoye6

个人项目:
https://gitee.com/falle22222n-leaves/vue_-book-manage-system

欢迎 Follow 和 Star~

相关推荐

GANs为何引爆机器学习?这篇基于TensorFlow的实例教程为你解惑!

「机器人圈导览」:生成对抗网络无疑是机器学习领域近三年来最火爆的研究领域,相关论文层出不求,各种领域的应用层出不穷。那么,GAN到底如何实践?本文编译自Medium,该文作者以一朵玫瑰花为例,详细阐...

高丽大学等机构联合发布StarGAN:可自定义表情和面部特征

原文来源:arXiv、GitHub作者:YunjeyChoi、MinjeChoi、MunyoungKim、Jung-WooHa、SungKim、JaegulChoo「雷克世界」编译:嗯~...

TensorFlow和PyTorch相继发布最新版,有何变化

原文来源:GitHub「机器人圈」编译:嗯~阿童木呀、多啦A亮Tensorflow主要特征和改进在Tensorflow库中添加封装评估量。所添加的评估量列表如下:1.深度神经网络分类器(DNNCl...

「2022 年」崔庆才 Python3 爬虫教程 - 深度学习识别滑动验证码缺口

上一节我们使用OpenCV识别了图形验证码躯壳欧。这时候就有朋友可能会说了,现在深度学习不是对图像识别很准吗?那深度学习可以用在识别滑动验证码缺口位置吗?当然也是可以的,本节我们就来了解下使用深度...

20K star!搞定 LLM 微调的开源利器

LLM(大语言模型)微调一直都是老大难问题,不仅因为微调需要大量的计算资源,而且微调的方法也很多,要去尝试每种方法的效果,需要安装大量的第三方库和依赖,甚至要接入一些框架,可能在还没开始微调就已经因为...

大模型DeepSeek本地部署后如何进行自定义调整?

1.理解模型架构a)查看深度求索官方文档或提供的源代码文件,了解模型的结构、输入输出格式以及支持的功能。模型是否为预训练权重?如果是,可以在预训练的基础上进行微调(Fine-tuning)。是否需要...

因配置不当,约5000个AI模型与数据集在公网暴露

除了可访问机器学习模型外,暴露的数据还可能包括训练数据集、超参数,甚至是用于构建模型的原始数据。前情回顾·人工智能安全动态向ChatGPT植入恶意“长期记忆”,持续窃取用户输入数据多模态大语言模型的致...

基于pytorch的深度学习人员重识别

基于pytorch的深度学习人员重识别Torchreid是一个库。基于pytorch的深度学习人员重识别。特点:支持多GPU训练支持图像的人员重识别与视频的人员重识别端到端的训练与评估简单的re...

DeepSeek本地部署:轻松训练你的AI模型

引言:为什么选择本地部署?在AI技术飞速发展的今天,越来越多的企业和个人希望将AI技术应用于实际场景中。然而,对于一些对数据隐私和计算资源有特殊需求的用户来说,云端部署可能并不是最佳选择。此时,本地部...

谷歌今天又开源了,这次是Sketch-RNN

前不久,谷歌公布了一项最新技术,可以教机器画画。今天,谷歌开源了代码。在我们研究其代码之前,首先先按要求设置Magenta环境。(https://github.com/tensorflow/magen...

Tensorflow 使用预训练模型训练的完整流程

前面已经介绍了深度学习框架Tensorflow的图像的标注和训练数据的准备工作,本文介绍一下使用预训练模型完成训练并导出训练的模型。1.选择预训练模型1.1下载预训练模型首先需要在Tensorf...

30天大模型调优学习计划(30分钟训练大模型)

30天大模型调优学习计划,结合Unsloth和Lora进行大模型微调,掌握大模型基础知识和调优方法,熟练应用。第1周:基础入门目标:了解大模型基础并熟悉Unsloth等工具的基本使用。Day1:大模...

python爬取喜马拉雅音频,json参数解析

一.抓包分析json,获取加密方式1.抓包获取音频界面f12打开抓包工具,播放一个(非vip)视频,点击“媒体”单击打开可以复制URL,发现就是我们要的音频。复制“CKwRIJEEXn-cABa0Tg...

五、JSONPath使用(Python)(json数据python)

1.安装方法pipinstalljsonpath2.jsonpath与Xpath下面表格是jsonpath语法与Xpath的完整概述和比较。Xpathjsonpath概述/$根节点.@当前节点...

Python网络爬虫的时候json=就是让你少写个json.dumps()

大家好,我是皮皮。一、前言前几天在Python白银交流群【空翼】问了一个Python网络爬虫的问题,提问截图如下:登录请求地址是这个:二、实现过程这里【甯同学】给了一个提示,如下所示:估计很多小伙伴和...