编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

开源免费的行为验证码(滑动拼图、点选文字)- AJ-Captcha

wxchong 2024-07-16 10:38:25 开源技术 13 ℃ 0 评论

简介

AJ-Captcha行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。后端提供Java实现,前端提供了php、angular、html、vue、uni-app、flutter、android、ios等代码示例。


源码仓库以及协议

源码地址:https://gitee.com/anji-plus/captcha

基于apache2协议,开源免费商用,但需要保留版权声明

Apache 2.0 开源协议具有以下特点:

1. 授权:该协议允许任何人自由使用、复制、修改、分发和销售被许可软件的副本。

2. 版权声明:被许可软件的副本必须包含原始版权声明和许可声明。

3. 专利授权:该协议授予了对软件相关专利的非专属授权,这意味着使用该软件的人不会受到专利侵权的指控。

4. 责任限制:被许可软件是按"原样"提供的,没有任何明示或暗示的担保和条件。使用者对软件的使用有责任承担风险。

5. 分发修改版本:使用者可以基于被许可软件创建衍生作品,并将其分发。然而,衍生作品必须遵循Apache 2.0协议,并包含相应的版权声明和许可声明。

总体而言,Apache 2.0 开源协议提供了灵活的许可方式,鼓励创新和共享。它广泛应用于许多开源软件项目,包括Apache HTTP服务器等。

项目启动

  • 后端工程

提供了go、php、java(springboot、springmvc)多种版本,我们这里以springboot为例

直接运行 StartApplication.java,配置文件先不修改 ,使用默认的,我们一会儿再来看看都有些什么配置项

  • 前端

提供的版本有很多,我们这里以vue为例演示

本地启动执行命令,记得修改接口地址

npm install
npm run dev

启动成功后可以看到演示界面

功能体验

进入内部页面后,还有前端代码集成示例,可以非常方便的集成到自己的项目中去

技术细节

  • 整体时序图
  • 后端的配置项

captcha/service/springboot/src/resources/application.properties

spring.application.name=captcha-service
server.port=8080

# 滑动验证,底图路径,不配置将使用默认图片
# 支持全路径
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/jigsaw
aj.captcha.jigsaw=classpath:images/jigsaw
# 滑动验证,底图路径,不配置将使用默认图片
# 支持全路径
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-click
aj.captcha.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...
aj.captcha.cache-type=local
# local缓存的阈值,达到这个值,清除缓存
#aj.captcha.cache-number=1000
# local定时清除过期缓存(单位秒),设置为0代表不执行
#aj.captcha.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两种都实例化。
aj.captcha.type=default
# 汉字统一使用Unicode,保证程序通过@value读取到是中文,可通过这个在线转换
# https://tool.chinaz.com/tools/unicode.aspx 中文转Unicode
# 右下角水印文字(我的水印)
aj.captcha.water-mark=我的水印
# 右下角水印字体(不配置时,默认使用文泉驿正黑)
# 由于宋体等涉及到版权,我们jar中内置了开源字体【文泉驿正黑】
# 方式一:直接配置OS层的现有的字体名称,比如:宋体
# 方式二:自定义特定字体,请将字体放到工程resources下fonts文件夹,支持ttf\ttc\otf字体
# aj.captcha.water-font=WenQuanZhengHei.ttf
# 点选文字验证码的文字字体(文泉驿正黑)
# aj.captcha.font-type=WenQuanZhengHei.ttf
# 校验滑动拼图允许误差偏移量(默认5像素)
aj.captcha.slip-offset=5
# aes加密坐标开启或者禁用(true|false)
aj.captcha.aes-status=true
# 滑动干扰项(0/1/2)
aj.captcha.interference-options=2

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


aj.captcha.history-data-clear-enable=false

# 接口请求次数一分钟限制是否开启 true|false
aj.captcha.req-frequency-limit-enable=false
# 验证失败5次,get接口锁定
aj.captcha.req-get-lock-limit=5
# 验证失败后,锁定时间间隔,s
aj.captcha.req-get-lock-seconds=360
# get接口一分钟内请求数限制
aj.captcha.req-get-minute-limit=30
# check接口一分钟内请求数限制
aj.captcha.req-check-minute-limit=30
# verify接口一分钟内请求数限制(暂用不上,可后台直接调用captchaService)
#aj.captcha.req-verify-minute-limit=30

可以看到后端可以配置的选项还是很多的,可以切换缓存类型,展示底图,接口限制等等

  • 接口分析

我们尝试滑动一次,发现在整个请求过程中,前端向后端发出了2个接口请求

第1个是滑动停止时,带着滑动码类型和位置信息请求check校验接口,如果滑动得是正确得位置,则里面得result字段为ture

http://localhost:8080/captcha/check

{"captchaType":"blockPuzzle","pointJson":"Ld/yNtPlENUtOoX2qdKHvNO5y/X8LU+vOUPWfitmrjc=","token":"3d5591a033d8482c89e0a77f477a9365"}

{
"repCode": "0000",
"repMsg": null,
"repData": {
"captchaId": null,
"projectCode": null,
"captchaType": "blockPuzzle",
"captchaOriginalPath": null,
"captchaFontType": null,
"captchaFontSize": null,
"secretKey": null,
"originalImageBase64": null,
"point": null,
"jigsawImageBase64": null,
"wordList": null,
"pointList": null,
"pointJson": "Ld/yNtPlENUtOoX2qdKHvNO5y/X8LU+vOUPWfitmrjc=",
"token": "3d5591a033d8482c89e0a77f477a9365",
"result": true,
"captchaVerification": null,
"clientUid": null,
"ts": null,
"browserInfo": null
},
"success": true
}

第2个其实是获取图片验证码得请求,是因为第一个我们滑动到了正确位置,所以界面刷新又获取了一次新得图片验证码

http://localhost:8080/captcha/get

实际上真正做滑动校验得是/check 这个接口

src/main/java/com/anji/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java

public ResponseModel check(CaptchaVO captchaVO) {
		ResponseModel r = super.check(captchaVO);
		if(!validatedReq(r)){
			return r;
		}
        //取坐标信息
        String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
        if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
        }
        String s = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
        //验证码只用一次,即刻失效
        CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
        PointVO point = null;
        PointVO point1 = null;
        String pointJson = null;
        try {
            point = JsonUtil.parseObject(s, PointVO.class);
            //aes解密
            pointJson = decrypt(captchaVO.getPointJson(), point.getSecretKey());
            point1 = JsonUtil.parseObject(pointJson, PointVO.class);
        } catch (Exception e) {
            logger.error("验证码坐标解析失败", e);
            afterValidateFail(captchaVO);
            return ResponseModel.errorMsg(e.getMessage());
        }
        if (point.x - Integer.parseInt(slipOffset) > point1.x
                || point1.x > point.x + Integer.parseInt(slipOffset)
                || point.y != point1.y) {
            afterValidateFail(captchaVO);
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
        }
        //校验成功,将信息存入缓存
        String secretKey = point.getSecretKey();
        String value = null;
        try {
            value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(pointJson), secretKey);
        } catch (Exception e) {
            logger.error("AES加密失败", e);
            afterValidateFail(captchaVO);
            return ResponseModel.errorMsg(e.getMessage());
        }
        String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
        CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
        captchaVO.setResult(true);
        captchaVO.resetClientFlag();
        return ResponseModel.successData(captchaVO);
    }

分析一下接口的实现,经过了坐标解密(滑块图的坐标信息是通过加密后传输给前端),坐标对比,需要滑动的位置在允许的范围内才认为滑动位置正确

再来看看验证码生成的实现

src/main/java/com/anji/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java

public ResponseModel get(CaptchaVO captchaVO) {
		ResponseModel r = super.get(captchaVO);
		if(!validatedReq(r)){
			return r;
		}
        //原生图片
        BufferedImage originalImage = ImageUtils.getOriginal();
        if (null == originalImage) {
            logger.error("滑动底图未初始化成功,请检查路径");
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
        }
        //设置水印
        Graphics backgroundGraphics = originalImage.getGraphics();
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        backgroundGraphics.setFont(waterMarkFont);
        backgroundGraphics.setColor(Color.white);
        backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - (HAN_ZI_SIZE / 2) + 7);

        //抠图图片
        String jigsawImageBase64 = ImageUtils.getslidingBlock();
        BufferedImage jigsawImage = ImageUtils.getBase64StrToImage(jigsawImageBase64);
        if (null == jigsawImage) {
            logger.error("滑动底图未初始化成功,请检查路径");
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
        }
        CaptchaVO captcha = pictureTemplatesCut(originalImage, jigsawImage, jigsawImageBase64);
        if (captcha == null
                || StringUtils.isBlank(captcha.getJigsawImageBase64())
                || StringUtils.isBlank(captcha.getOriginalImageBase64())) {
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
        }
        return ResponseModel.successData(captcha);
    }

大致过程:加载验证码底图、随机从底图中扣取一个部分作为要滑动的小图,最后把两个图都以base64图片形式返回给前端

总结

1、本篇我们介绍了一个开源免费的行为验证码的项目

2、完成了项目的实际搭建和功能体验

3、我们初步分析了后端的核心代码,了解了滑动验证码的生成和校验逻辑

4、整体来说这个项目非常完整,并且提供了很多的终端实现(常见的基本上都支持了,html,vue,小程序终端,原生ios android等),并且后端也是非常容易集成到自己的项目,拿着demo版本修改一下即可

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表