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