当前位置: 首页 > news >正文

行为验证码 AJ-Captcha 使用文档

AJ-Captcha 使用文档

一、环境准备

1. 添加依赖

<dependency><groupId>com.anji-plus</groupId><artifactId>captcha-spring-boot-starter</artifactId><version>1.4.0</version> <!-- 请使用最新版本 -->
</dependency><!-- Redis 依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. Redis 配置

application.yml 中配置 Redis 连接:

spring:redis:host: localhostport: 6379password: database: 0timeout: 3000

二、核心实现

1. Redis 缓存服务实现

import com.anji.captcha.service.CaptchaCacheService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;import java.util.Collections;
import java.util.concurrent.TimeUnit;@Setter
@Slf4j
public class CaptchaCacheRedisImpl implements CaptchaCacheService {@Overridepublic String type() {return "redis";}private static final String LUA_SCRIPT = "local key = KEYS[1] " +"local incrementValue = tonumber(ARGV[1]) " +"if redis.call('EXISTS', key) == 1 then " +"    return redis.call('INCRBY', key, incrementValue) " +"else " +"    return 0 " +"end";private StringRedisTemplate stringRedisTemplate;@Overridepublic void set(String key, String value, long expiresInSeconds) {log.debug("图形验证码设置 key={}, value={}, expires={}", key, value, expiresInSeconds);stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);}@Overridepublic boolean exists(String key) {log.debug("图形验证码校验 key 是否存在: {}", key);return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));}@Overridepublic void delete(String key) {log.debug("图形验证码删除 key: {}", key);stringRedisTemplate.delete(key);}@Overridepublic String get(String key) {log.debug("图形验证码获取 key: {}", key);return stringRedisTemplate.opsForValue().get(key);}@Overridepublic Long increment(String key, long val) {log.debug("图形验证码 key={} 增加值={}", key, val);RedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);return stringRedisTemplate.execute(script,Collections.singletonList(key),String.valueOf(val));}@Overridepublic void setExpire(String key, long expireSeconds) {log.debug("图形验证码设置 key={} 过期时间={}", key, expireSeconds);stringRedisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS);}
}

2. SPI 配置

resources 目录下创建:

src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService

文件内容:

com.yourpackage.CaptchaCacheRedisImpl

3. Spring Boot 配置类

import com.anji.captcha.properties.AjCaptchaProperties;
import com.anji.captcha.service.CaptchaCacheService;
import com.anji.captcha.service.impl.CaptchaServiceFactory;
import lombok.RequiredArgsConstructor;
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;@Configuration
@RequiredArgsConstructor
public class CaptchaConfig {private final StringRedisTemplate stringRedisTemplate;@Bean(name = "AjCaptchaCacheService")@Primarypublic CaptchaCacheService captchaCacheService(AjCaptchaProperties config) {CaptchaCacheService service = CaptchaServiceFactory.getCache(config.getCacheType().name());if (service instanceof CaptchaCacheRedisImpl) {((CaptchaCacheRedisImpl) service).setStringRedisTemplate(stringRedisTemplate);}return service;}@Bean@Primarypublic AjCaptchaProperties ajCaptchaProperties() {AjCaptchaProperties properties = new AjCaptchaProperties();properties.setCacheType(AjCaptchaProperties.StorageType.redis);properties.setWaterMark("我的水印");// 可选:其他配置properties.setClickWordCount(4);              // 点选文字数量properties.setHistoryDataClearEnable(true);   // 是否清除历史数据properties.setInterferenceOptions(3);         // 干扰选项数量properties.setReqFrequencyLimitEnable(true);  // 启用频率限制return properties;}
}

4. HTTP 工具类

import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;public class HttpRequestUtil {public static String getRemoteId(HttpServletRequest request) {String xfwd = request.getHeader("X-Forwarded-For");String ip = getRemoteIpFromXfwd(xfwd);String ua = request.getHeader("user-agent");if (StringUtils.isNotBlank(ip)) {return ip + "_" + ua;}return request.getRemoteAddr() + "_" + ua;}private static String getRemoteIpFromXfwd(String xfwd) {if (StringUtils.isNotBlank(xfwd)) {String[] ipList = xfwd.split(",");return StringUtils.trim(ipList[0]);}return null;}
}

三、控制器实现

验证码控制器

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.RequestBody;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestController
public class CaptchaController {@Autowiredprivate CaptchaService captchaService;/*** 获取验证码*/@PostMapping("/captcha/get")public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {assert request.getRemoteHost() != null;data.setBrowserInfo(HttpRequestUtil.getRemoteId(request));return captchaService.get(data);}/*** 校验验证码*/@PostMapping("/captcha/check")public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {data.setBrowserInfo(HttpRequestUtil.getRemoteId(request));return captchaService.check(data);}/*** 最终校验验证码*/@PostMapping("/captcha/verify")public ResponseModel verify(@RequestBody CaptchaVO data) {return captchaService.verification(data);}
}

四、前端集成

基本使用示例(Vue)

<template><div><captcharef="captchaRef":mode="mode":api-url="apiUrl"@success="onCaptchaSuccess"@error="onCaptchaError"/><button @click="submitForm">提交</button></div>
</template><script>
import { Captcha } from '@xingyuv/captcha-plus-vue';export default {components: { Captcha },data() {return {mode: 'blockPuzzle', // 验证码类型: blockPuzzle(滑块)/clickWord(点选)apiUrl: '/captcha/get',captchaToken: '',businessId: `ORDER_${Date.now()}`};},methods: {onCaptchaSuccess(data) {this.captchaToken = data.token;},onCaptchaError(error) {console.error('验证码加载失败:', error);},async submitForm() {try {// 1. 先进行验证码校验const checkResponse = await this.$axios.post('/captcha/check', {token: this.captchaToken,point: this.$refs.captchaRef.getPointData()});if (!checkResponse.data.success) {throw new Error('验证码校验失败');}// 2. 提交业务请求(携带验证token)const response = await this.$axios.post('/api/submit', {...this.formData,captchaToken: this.captchaToken,businessId: this.businessId});// 3. 处理业务响应...} catch (error) {// 验证失败重置验证码this.$refs.captchaRef.reset();}}}
};
</script>

五、API 说明

1. 获取验证码接口 (/captcha/get)

  • 请求方法: POST
  • 请求参数:
    {"captchaType": "blockPuzzle" // 可选: 验证码类型
    }
    
  • 响应示例:
    {"repCode": "0000","repData": {"originalImageBase64": "iVBORw0KGgoAAAANSUhEUgAA...","sliderImageBase64": "iVBORw0KGgoAAAANSUhEUgAA...","point": {"x": 120, "y": 80},"token": "b9f8a7c6-5d4e-3f2a-1b0c-9d8e7f6a5b4c"}
    }
    

2. 校验验证码接口 (/captcha/check)

  • 请求方法: POST
  • 请求参数:
    {"token": "b9f8a7c6-5d4e-3f2a-1b0c-9d8e7f6a5b4c","point": {"x": 118, "y": 82}
    }
    
  • 响应示例:
    {"repCode": "0000","repData": true
    }
    

3. 最终验证接口 (/captcha/verify)

  • 请求方法: POST
  • 请求参数:
    {"token": "b9f8a7c6-5d4e-3f2a-1b0c-9d8e7f6a5b4c","businessId": "ORDER_20230001" // 可选: 业务关联ID
    }
    
  • 响应示例:
    {"repCode": "0000","repData": true
    }
    

六、注意事项

1. 安全配置建议

aj:captcha:aes-status: true              # 启用坐标加密slip-offset: 5                # 滑块容错像素值req-get-minute-limit: 100     # 每分钟获取验证码限制req-check-minute-limit: 200   # 每分钟校验验证码限制req-verify-minute-limit: 300  # 每分钟二次验证限制

2. 性能优化

  1. Redis 优化:

    • 使用 Redis 集群
    • 设置合理的内存淘汰策略
    # Redis 配置建议
    maxmemory 1gb
    maxmemory-policy allkeys-lru
    
  2. 图片资源优化:

    • 压缩验证码图片
    • 使用 CDN 分发静态资源
    • 启用浏览器缓存
  3. 连接池配置:

    spring:redis:lettuce:pool:max-active: 100max-idle: 50min-idle: 10max-wait: 5000
    

3. 常见问题解决

问题原因解决方案
验证码图片加载失败资源路径配置错误检查 aj.captcha.jigsawaj.captcha.pic-click 配置
验证总是失败客户端坐标计算错误检查前端 DPI 缩放比例计算
Redis 连接超时Redis 配置不当增加超时时间,优化网络
SPI 加载失败文件位置错误确保 SPI 文件在 META-INF/services 目录下
高并发下验证失败Redis 性能瓶颈使用 Redis 集群,增加连接池

4. 最佳实践

  1. 业务集成:

    @PostMapping("/login")
    public ResponseModel login(@RequestBody LoginRequest request) {// 1. 执行最终验证CaptchaVO captchaVO = new CaptchaVO();captchaVO.setToken(request.getCaptchaToken());ResponseModel verifyResponse = captchaService.verification(captchaVO);// 2. 验证失败直接返回if (!verifyResponse.isSuccess()) {return ResponseModel.error("验证码失效");}// 3. 执行登录逻辑return userService.login(request.getUsername(), request.getPassword());
    }
    
  2. 设备指纹增强:

    public static String getEnhancedRemoteId(HttpServletRequest request) {String remoteId = getRemoteId(request);String deviceId = request.getHeader("Device-ID");String sessionId = request.getSession().getId();return DigestUtils.md5Hex(remoteId + "_" + deviceId + "_" + sessionId);
    }
    
  3. 监控与日志:

    • 记录验证失败日志
    • 监控验证码请求频率
    • 设置验证失败告警阈值

七、架构设计

客户端
获取验证码 /captcha/get
生成验证码
Redis 缓存验证数据
返回验证码图片和token
校验验证码 /captcha/check
Redis 获取验证数据
验证坐标/轨迹
返回校验结果
业务请求
最终验证 /captcha/verify
Redis 验证并删除token
执行业务逻辑

八、扩展开发

1. 自定义验证策略

public class CustomCaptchaService extends DefaultCaptchaServiceImpl {@Overridepublic ResponseModel check(CaptchaVO data) {// 高风险设备使用更严格验证if (isHighRiskDevice(data.getBrowserInfo())) {setSlipOffset(3); // 减小容错范围} else {setSlipOffset(5); // 正常容错范围}return super.check(data);}private boolean isHighRiskDevice(String browserInfo) {// 自定义风险判断逻辑return false;}
}

2. 动态水印

@Bean
@Primary
public AjCaptchaProperties ajCaptchaProperties(HttpServletRequest request) {AjCaptchaProperties properties = new AjCaptchaProperties();// 根据用户动态设置水印User currentUser = getCurrentUser(request);properties.setWaterMark(currentUser.getUsername() + "的水印");return properties;
}

九、版本升级

  • 定期检查官方仓库获取最新版本:AJ-Captcha GitHub
  • 升级前备份配置和自定义实现
  • 测试环境充分验证后再上线

通过以上配置和实现,您可以轻松地在 Spring Boot 项目中集成 AJ-Captcha 验证码服务,提供强大的安全防护能力。

http://www.lqws.cn/news/512515.html

相关文章:

  • Golang Kratos 系列:领域层model定义是自洽还是直接依赖第三方(三)
  • C++字符串的行输入
  • MySQL之SQL性能优化策略
  • 《仿盒马》app开发技术分享-- 兑换列表展示(68)
  • git操作练习(3)
  • 【Python-Day 29】万物皆对象:详解 Python 类的定义、实例化与 `__init__` 方法
  • SQL Server从入门到项目实践(超值版)读书笔记 18
  • git commit --no-verify -m ““ 命令的作用是什么
  • LangChain网页自动化PlayWrightBrowserToolkit
  • Python训练营-Day40-训练和测试的规范写法
  • maven:迁移到 Maven Central 后 pom.xml的配置步骤
  • 马克思主义基本原理期末复习下
  • HarmonyOS开发基础 --鸿蒙仓颉语言基础语法入门
  • 基于元学习的回归预测模型如何设计?
  • 3D重建任务中的显式学习和隐式学习
  • 脉内频率捷变LFM信号
  • 【神经网络预测】基于LSTM、PSO - LSTM、随机森林和多项式拟合的火力机组排放预测
  • 解锁Selenium:Web自动化的常用操作秘籍
  • 超实用教程:n8n + MCP(MinIO Client Processor)构建智能文件处理流水线 - 从零部署到企业级自动化实战​
  • ubuntu20.04安装多版本python时,如何使用sudo python3.10
  • Linux离线搭建Jenkins
  • 有AI后,还用学编程吗?
  • 哈希表理论与算法总结
  • 飞往大厂梦之算法提升-day08
  • Java实现简易即时通讯系统
  • leetcode230-二叉搜索树中第K小的元素
  • OSS与NAS混合云存储架构:非结构化数据统一管理实战
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | MovieApp(电影卡片组件)
  • AI时代工具:AIGC导航——AI工具集合
  • 60天python训练营打卡day41