本文目录导读:

在Java中验证验证码有效性,通常有两种场景:前后端分离(后端生成+校验)和传统单体应用(Session存储),下面给出3种主流验证码库的实现案例。
基础逻辑:验证码验证的本质
无论用哪种库,核心步骤都是:
- 生成阶段:生成验证码文本 → 存入服务端(Session/Redis) → 返回图片给前端
- 验证阶段:用户提交文本 → 从服务端取出正确文本 → 比对(通常忽略大小写)→ 清除/标记已使用
案例一:使用 Kaptcha(传统 Session 方案)
适用于:单体应用、不涉及分布式 Session 的项目。
1 依赖(Maven)
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2 生成验证码 Controller
@GetMapping("/captcha")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 设置响应类型
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
// 2. 生成验证码文本(Kaptcha默认使用DefaultKaptcha)
String capText = captchaProducer.createText();
// 3. 存入 Session(key 自定义,如 "captchaCode")
request.getSession().setAttribute("captchaCode", capText);
// 4. 生成图片并写入响应流
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
out.flush();
out.close();
}
3 验证验证码有效性 Controller
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
@RequestParam String captchaInput,
HttpServletRequest request) {
// 1. 从 Session 获取正确验证码
String correct = (String) request.getSession().getAttribute("captchaCode");
// 2. 清除验证码(防止重复使用)
request.getSession().removeAttribute("captchaCode");
// 3. 有效性校验
if (correct == null || !correct.equalsIgnoreCase(captchaInput)) { // 忽略大小写
return "验证码错误或已过期";
}
// 4. 验证通过,继续业务逻辑
return "登录成功";
}
案例二:使用 EasyCaptcha(支持 Redis + 分布式)
适用于:前后端分离、分布式部署。
1 依赖
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
2 生成验证码(UUID + Redis 存储)
@GetMapping("/captcha")
public Result<Map<String, String>> getCaptcha() {
// 1. 生成验证码对象(数字+字母,4位)
SpecCaptcha captcha = new SpecCaptcha(130, 48, 4);
// 2. 获取文本(如 "A3bC")
String code = captcha.text().toLowerCase(); // 统一转小写存储
// 3. 生成唯一标识符(作为Redis的key)
String uuid = UUID.randomUUID().toString().replace("-", "");
// 4. 存入 Redis(过期时间 5 分钟)
redisTemplate.opsForValue().set(RedisKeys.CAPTCHA_KEY + uuid, code, 5, TimeUnit.MINUTES);
// 5. 返回给前端:uuid(作为凭证) + 图片base64
Map<String, String> map = new HashMap<>();
map.put("captchaKey", uuid);
map.put("captchaImg", captcha.toBase64()); // 前端直接<img src="data:image/...">显示
return Result.success(map);
}
3 验证验证码有效性 Controller
@PostMapping("/login")
public Result<?> login(@RequestBody LoginForm form) {
// 1. 从请求中拿到 captchaKey 和前端输入的 captchaInput
String captchaKey = form.getCaptchaKey();
String captchaInput = form.getCaptchaInput();
// 2. 从 Redis 取出正确的验证码
String correctCode = redisTemplate.opsForValue().get(RedisKeys.CAPTCHA_KEY + captchaKey);
// 3. **立即删除**(防止多次使用)
redisTemplate.delete(RedisKeys.CAPTCHA_KEY + captchaKey);
// 4. 有效性校验
if (correctCode == null) {
return Result.error("验证码已过期,请重新获取");
}
if (!correctCode.equalsIgnoreCase(captchaInput)) {
return Result.error("验证码错误");
}
// 5. 验证通过,继续业务
return Result.success("登录成功");
}
关键验证规则(必须注意)
1 大小写不敏感
推荐统一转为小写后再比较:
correctCode.equalsIgnoreCase(inputCode); // 或者 correctCode.toLowerCase().equals(inputCode.toLowerCase());
2 一次性使用
- 无论是 Session 还是 Redis,验证成功后必须立即删除。
- 防止同一个验证码被重复提交。
3 过期机制
- Session:默认 30 分钟失效,建议手动设置更短(如 5 分钟)。
- Redis:设置过期时间
5, TimeUnit.MINUTES。
4 防暴力破解
- 同一 IP 短时间内验证失败次数过多 → 临时锁定(可加计数器到 Redis)。
- 验证码本身可以加 干扰线、扭曲、噪点(EasyCaptcha / Kaptcha 配置)。
完整校验流程图
前端 后端 Redis / Session
───────── ────────── ────────────
请求获取验证码 ──→ 生成code + 存储Code ──→ 保存code
↓
返回{img, key} ←── code不返回给前端
↓
用户填写验证码 + key
↓
提交表单 ──→ 取出code(根据key)
↓
比较code ←── 忽略大小写比对比
↓
删除key对应的code ──→ 清除
↓
返回成功/失败
| 场景 | 推荐技术 | 存储方案 | 验证核心 |
|---|---|---|---|
| 单体应用 | Kaptcha | Session | session.getAttribute |
| 分布式 / 前后端分离 | EasyCaptcha + Redis | Redis + UUID | redis.get(key) + 删除 |
| 高安全需求 | 算术验证码 / 滑动验证 | 均可 | 同上 + 轨迹校验 |
验证码有效验证的黄金三原则:
- 存储 ≠ 返回(code不进返回体)
- 验证即删(一次有效)
- 忽略大小写