行为验证码 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. 性能优化
-
Redis 优化:
- 使用 Redis 集群
- 设置合理的内存淘汰策略
# Redis 配置建议 maxmemory 1gb maxmemory-policy allkeys-lru
-
图片资源优化:
- 压缩验证码图片
- 使用 CDN 分发静态资源
- 启用浏览器缓存
-
连接池配置:
spring:redis:lettuce:pool:max-active: 100max-idle: 50min-idle: 10max-wait: 5000
3. 常见问题解决
问题 | 原因 | 解决方案 |
---|---|---|
验证码图片加载失败 | 资源路径配置错误 | 检查 aj.captcha.jigsaw 和 aj.captcha.pic-click 配置 |
验证总是失败 | 客户端坐标计算错误 | 检查前端 DPI 缩放比例计算 |
Redis 连接超时 | Redis 配置不当 | 增加超时时间,优化网络 |
SPI 加载失败 | 文件位置错误 | 确保 SPI 文件在 META-INF/services 目录下 |
高并发下验证失败 | Redis 性能瓶颈 | 使用 Redis 集群,增加连接池 |
4. 最佳实践
-
业务集成:
@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()); }
-
设备指纹增强:
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); }
-
监控与日志:
- 记录验证失败日志
- 监控验证码请求频率
- 设置验证失败告警阈值
七、架构设计
八、扩展开发
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 验证码服务,提供强大的安全防护能力。