当前位置: 首页 > 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 验证码服务,提供强大的安全防护能力。

相关文章:

  • 苏州做网站建设公司公司怎么在网上推广
  • 免费建站网站靠谱吗找网络公司做推广费用
  • 服务器架构做网站惠东seo公司
  • html代码做的网站seo技术助理
  • 域名解析错误怎么解决网络seo是什么工作
  • 网站建设功能seo首页优化
  • 计算机网络第九章——数据链路层《介质访问控制》
  • CDN+OSS边缘加速实践:动态压缩+智能路由降低30%视频流量成本(含带宽峰值监控与告警配置)
  • SM4算法的Verilog流水线实现(带测试)
  • 最方便的应用构建——利用云原生快速搭建本地deepseek知识仓库
  • IoTDB的基本概念及常用命令
  • 内存泄漏系列专题分析之二十四:内存泄漏测试Camera相机进程内存指标分布report概述
  • 02-StarRocks数据导入导出FAQ
  • 猿人学js逆向比赛第一届第十二题
  • MemcacheRedis--缓存服务器理论
  • MR7350用TTL刷机救砖过程
  • 桌面小屏幕实战课程:DesktopScreen 8 非易失性存储器NVS
  • 安卓9.0系统修改定制化____安卓9.0修改 默认开启开发者选项与usb调试的操作步骤解析 十一
  • Vue项目使用defer优化页面白屏,性能优化提升,秒加载!!!
  • 大白话蓝牙中的RPC:Remote Procedure Call远程过程调用
  • 夏季小学期
  • DEYOLO 全面复现,将双增强跨模态目标检测网络 DEYOLO 融合到 YOLOFuse 框架
  • 微信小程序节点相关总结
  • 入门级STM32F103C8T6无人机(原理图其一)
  • Proteus 8.17下载安装保姆级教程【2025最新版】附安装包
  • Android Navigation 原理解析