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

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

首页休闲益智滑动拼图更新时间:2024-09-20
简介

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版本修改一下即可

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved