跳到主要内容

验证码

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 或自备共享存储实现。