采用嵌入式集成方式,接入方便,安全,高效。抛弃了传统字符型验证码展示-填写字符-比对答案的流程,采用验证码展示-采集用户行为-分析用户行为流程,用户只需要产生指定的行为轨迹,不需要键盘手动输入,极大优化了传统验证码用户体验不佳的问题;同时,快速、准确的返回人机判定结果
AJ-Captcha行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。后端提供Java实现,前端提供了php、angular、html、vue、uni-app、flutter、android、ios等代码示例。
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>Captcha</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.anji-plus</groupId><artifactId>spring-boot-starter-captcha</artifactId><version>1.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies></project>
spring.application.name=captcha-serviceserver.port=8080# 滑动验证,底图路径,不配置将使用默认图片# 支持全路径# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/jigsawaj.captcha.jigsaw=classpath:images/jigsaw# 滑动验证,底图路径,不配置将使用默认图片# 支持全路径# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-clickaj.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.BOLDaj.captcha.font-style=1#点选字体字体大小aj.captcha.font-size=25#点选文字个数,存在问题,暂不支持修改#aj.captcha.click-word-count=4aj.captcha.history-data-clear-enable=false# 接口请求次数一分钟限制是否开启 true|falseaj.captcha.req-frequency-limit-enable=false# 验证失败5次,get接口锁定aj.captcha.req-get-lock-limit=5# 验证失败后,锁定时间间隔,saj.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
package com.et.captcha.config;import com.et.captcha.service.CaptchaCacheServiceRedisImpl;import com.anji.captcha.properties.AjCaptchaProperties;import com.anji.captcha.service.CaptchaCacheService;import com.anji.captcha.service.impl.CaptchaServiceFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.redis.core.StringRedisTemplate;public class CaptchaConfig {StringRedisTemplate redisTemplate;public CaptchaCacheService captchaCacheService(AjCaptchaProperties config){//缓存类型redis/local/....CaptchaCacheService ret = CaptchaServiceFactory.getCache(config.getCacheType().name());if(ret instanceof CaptchaCacheServiceRedisImpl){((CaptchaCacheServiceRedisImpl)ret).setStringRedisTemplate(redisTemplate);}return ret;}}
CaptchaCacheService有很多种实现类,可以动态扩展,具体方式可以参考官方教程,下面提出默认local的实现方式DefaultCaptchaServiceImpl
package com.anji.captcha.service.impl;import com.anji.captcha.model.common.RepCodeEnum;import com.anji.captcha.model.common.ResponseModel;import com.anji.captcha.model.vo.CaptchaVO;import com.anji.captcha.service.CaptchaService;import com.anji.captcha.util.StringUtils;import java.util.Iterator;import java.util.Properties;public class DefaultCaptchaServiceImpl extends AbstractCaptchaService {public DefaultCaptchaServiceImpl() {}public String captchaType() {return "default";}public void init(Properties config) {Iterator var2 = CaptchaServiceFactory.instances.keySet().iterator();while(var2.hasNext()) {String s = (String)var2.next();if (!this.captchaType().equals(s)) {this.getService(s).init(config);}}}public void destroy(Properties config) {Iterator var2 = CaptchaServiceFactory.instances.keySet().iterator();while(var2.hasNext()) {String s = (String)var2.next();if (!this.captchaType().equals(s)) {this.getService(s).destroy(config);}}}private CaptchaService getService(String captchaType) {return (CaptchaService)CaptchaServiceFactory.instances.get(captchaType);}public ResponseModel get(CaptchaVO captchaVO) {if (captchaVO == null) {return RepCodeEnum.NULL_ERROR.parseError(new Object[]{"captchaVO"});} else {return StringUtils.isEmpty(captchaVO.getCaptchaType()) ? RepCodeEnum.NULL_ERROR.parseError(new Object[]{"类型"}) : this.getService(captchaVO.getCaptchaType()).get(captchaVO);}}public ResponseModel check(CaptchaVO captchaVO) {if (captchaVO == null) {return RepCodeEnum.NULL_ERROR.parseError(new Object[]{"captchaVO"});} else if (StringUtils.isEmpty(captchaVO.getCaptchaType())) {return RepCodeEnum.NULL_ERROR.parseError(new Object[]{"类型"});} else {return StringUtils.isEmpty(captchaVO.getToken()) ? RepCodeEnum.NULL_ERROR.parseError(new Object[]{"token"}) : this.getService(captchaVO.getCaptchaType()).check(captchaVO);}}public ResponseModel verification(CaptchaVO captchaVO) {if (captchaVO == null) {return RepCodeEnum.NULL_ERROR.parseError(new Object[]{"captchaVO"});} else if (StringUtils.isEmpty(captchaVO.getCaptchaVerification())) {return RepCodeEnum.NULL_ERROR.parseError(new Object[]{"二次校验参数"});} 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();}}}
package com.anji.captcha.controller;import com.anji.captcha.model.common.ResponseModel;import com.anji.captcha.model.vo.CaptchaVO;import com.anji.captcha.service.CaptchaService;import com.anji.captcha.util.StringUtils;import javax.servlet.http.HttpServletRequest;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;public class CaptchaController {private CaptchaService captchaService;public CaptchaController() {}public ResponseModel get( CaptchaVO data, HttpServletRequest request) {assert request.getRemoteHost() != null;data.setBrowserInfo(getRemoteId(request));return this.captchaService.get(data);}public ResponseModel check( CaptchaVO data, HttpServletRequest request) {data.setBrowserInfo(getRemoteId(request));return this.captchaService.check(data);}public ResponseModel verify( CaptchaVO data, HttpServletRequest request) {return this.captchaService.verification(data);}public static final String getRemoteId(HttpServletRequest request) {String xfwd = request.getHeader("X-Forwarded-For");String ip = getRemoteIpFromXfwd(xfwd);String ua = request.getHeader("user-agent");return StringUtils.isNotBlank(ip) ? ip + ua : request.getRemoteAddr() + ua;}private static String getRemoteIpFromXfwd(String xfwd) {if (StringUtils.isNotBlank(xfwd)) {String[] ipList = xfwd.split(",");return StringUtils.trim(ipList[0]);} else {return null;}}}
package com.et.captcha.controller;import com.anji.captcha.model.common.ResponseModel;import com.anji.captcha.model.vo.CaptchaVO;import com.anji.captcha.service.CaptchaService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;/*** 后端二次校验接口示例*/public class LoginController {private CaptchaService captchaService;public ResponseModel get( String captchaVerification) {CaptchaVO captchaVO = new CaptchaVO();captchaVO.setCaptchaVerification(captchaVerification);ResponseModel response = captchaService.verification(captchaVO);if(response.isSuccess() == false){//验证码校验失败,返回信息告诉前端//repCode 0000 无异常,代表成功//repCode 9999 服务器内部异常//repCode 0011 参数不能为空//repCode 6110 验证码已失效,请重新获取//repCode 6111 验证失败//repCode 6112 获取验证码失败,请联系管理员}return response;}}
前端工程代码太多就不贴出来了,具体可以查看web-ui文件家里面的内容,以上只是一些关键代码,所有代码请参见下面代码仓库
https://github.com/Harries/springboot-demo
第一步:启动后端,启动service/springboot的StartApplication。
第二步:启动前端,终端进入文件夹view/vue,
yarn install
yarn run dev
浏览器登录 http://localhost:8081/#/

https://gitee.com/anji-plus/captcha
http://www.liuhaihua.cn/archives/710384.html
原文作者:HBLOG;如有侵权,请联系管理员删除。