验证码
Coco 验证码(coco-captcha)提供与类型无关的验证码生成/校验 SPI:图形(IMAGE)、滑块(SLIDER)、短信码(SMS_CODE)。框架自带本地参考实现(JDK 内置 AWT 渲染图形、数字滑块、数字短信码)与进程内答案存储;生产可注册自定义生成器或共享存储替换。默认关闭,coco.captcha.enabled=true 开启。
功能简介
- 三种验证码类型:
IMAGE下发 base64 PNG,SLIDER下发轨道宽度按偏移容差校验,SMS_CODE生成数字码(由业务经coco-notification下发,本模块不发送)。 - 答案不外泄:
CocoCaptcha拆成客户端挑战与服务端答案两部分,响应只回ClientView(无答案字段)。 - 单次核验:
CocoCaptchaStore.consume取出即删——无论比对成败,一个captchaId只能核验一次,防止答案重放与单码爆破。 - 可替换存储:默认
InMemoryCocoCaptchaStore仅适合单实例;多实例把store-type切到redis即用内置的 Lua 原子实现,也可提供自定义 Bean 接入其它共享存储。 - 业务生成器优先:注册自定义
CocoCaptchaGenerator覆盖同类型参考实现。
如何启用接入
1. 打开开关
coco:
captcha:
enabled: true
ttl: 2m # 验证码存活时间
image-enabled: true
slider-enabled: true
sms-code-enabled: true
image-length: 4 # 图形验证码字符数
sms-code-length: 6 # 短信码位数
slider-tolerance: 5 # 滑块偏移容差(像素)
2. 生成与校验
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
private final CocoCaptchaService captchaService;
public CaptchaController(CocoCaptchaService captchaService) {
this.captchaService = captchaService;
}
@GetMapping("/image")
public CocoCaptcha.ClientView image() {
// 返回 {captchaId, type, challenge},challenge 是 data:image/png;base64,...
return captchaService.generate(CocoCaptchaType.IMAGE);
}
@PostMapping("/verify")
public boolean verify(@RequestParam String captchaId, @RequestParam String code) {
return captchaService.verify(CocoCaptchaType.IMAGE, captchaId, code);
}
}
3. 集群部署切换到 Redis 存储
InMemoryCocoCaptchaStore 的答案只存在于当前 JVM。多实例部署下验证码在 A 实例生成、B 实例校验时取不到答案,核验必然失败——而且表现为"验证码错误",排查起来很隐蔽。
集群环境改用内置的 Redis 存储,只需声明 store-type:
coco:
captcha:
enabled: true
store-type: redis # 默认 in-memory
redis:
key-prefix: "coco:captcha:" # 可选
再引入 Spring Data Redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
RedisCocoCaptchaStore 用 Lua 脚本把 GET + DEL 合成一步,保证单次核验语义在并发下依然成立——分成两次往返会让两个并发请求读到同一个答案。键名对 captchaId 做 SHA-256 摘要,标识不会明文进入 Redis keyspace。
若设了 store-type: redis 但 classpath 上没有 Spring Data Redis,启动会直接失败并说明原因,不会静默回落到进程内存储。静默回落会让验证码在负载均衡后随机失败,却看起来一切正常。
多个 StringRedisTemplate Bean 时,用 coco.captcha.redis.template-bean-name 显式指定,或把目标 Bean 标记 @Primary;否则启动失败并列出候选,不会随机挑一个。
Redis 不可用时 consume 会直接抛出异常,不会退化成返回 null——null 的语义是"已过期或不存在",会被渲染成"验证码错误",用它兼表故障等于把正确答案判成错误,还会让故障从错误率里消失。
4. 自定义生成器或存储
@Bean
CocoCaptchaGenerator myImageGenerator() {
return new MyImageGenerator(); // supportedType() 返回 IMAGE,覆盖参考实现
}
@Bean
CocoCaptchaStore myCaptchaStore() {
return new MyOwnCaptchaStore(/* ... */); // 两种 store-type 下都生效
}
边界
- 不负责下发:短信码由业务经
coco-notification或自有渠道发送,本模块只生成与核验。 - 参考图形实现较弱:
ImageCocoCaptchaGenerator只有基础干扰线,不抗 OCR;高安全场景请注册自定义生成器。 - 滑块不含拼图图像:参考实现只做偏移容差校验,缺口拼图由业务生成器自行实现。
- 默认存储不跨实例:多实例部署务必切到
store-type: redis或自备共享存储实现。