三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

SpringBoot集成EasyCaptcha实现图片验证码全攻略

SpringBoot集成EasyCaptcha实现图片验证码全攻略

1. SpringBoot集成EasyCaptcha实现图片验证码全攻略

验证码作为现代Web应用的基础安全组件,几乎出现在所有需要防止机器恶意操作的场景中。最近在重构一个老项目时,我选择了EasyCaptcha作为验证码解决方案,这个轻量级库以其简洁的API和丰富的样式赢得了我的青睐。本文将完整记录从零开始集成EasyCaptcha的全过程,包含五种验证码样式的实现对比、性能优化技巧以及生产环境中的实战经验。

2. 环境准备与基础集成

2.1 项目初始化与依赖配置

首先通过Spring Initializr创建基础项目,我使用的环境组合是:

  • Spring Boot 3.1.5
  • JDK 17
  • Maven 3.8.6

在pom.xml中添加关键依赖:

<dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>1.6.2</version> </dependency>

注意:EasyCaptcha的groupId在2021年后从com.wf.captcha变更为com.github.whvcse,使用旧版本文档时需要注意区分。

2.2 基础配置类实现

创建CaptchaConfig配置类统一管理验证码参数:

@Configuration public class CaptchaConfig { @Bean public Producer captchaProducer() { // 默认配置:150x50像素,5位字符,无干扰线 DefaultKaptcha producer = new DefaultKaptcha(); Properties props = new Properties(); props.put("kaptcha.border", "no"); props.put("kaptcha.textproducer.font.color", "black"); props.put("kaptcha.textproducer.char.space", "5"); producer.setConfig(new Config(props)); return producer; } }

3. 五种验证码样式深度解析

3.1 算术验证码(ArithmeticCaptcha)

最常用的动态计算型验证码,适合需要平衡安全性与用户体验的场景:

@GetMapping("/math") public void mathCaptcha(HttpServletRequest request, HttpServletResponse response) { ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48); captcha.setLen(3); // 设置运算位数 captcha.getArithmeticString(); // 获取运算公式如"3+7=?" request.getSession().setAttribute("captcha", captcha.text()); captcha.out(response.getOutputStream()); }

特点分析

  • 生成形如"3+7=?"的数学题
  • 用户需要计算结果作为验证码
  • 防OCR效果较好且用户友好

3.2 中文验证码(ChineseCaptcha)

中文验证码在防机器识别方面表现突出:

@GetMapping("/chinese") public void chineseCaptcha(HttpServletRequest request, HttpServletResponse response) { ChineseCaptcha captcha = new ChineseCaptcha(150, 50); captcha.setLen(4); // 4个汉字 request.getSession().setAttribute("captcha", captcha.text()); captcha.out(response.getOutputStream()); }

优化技巧

  • 配合字体缓存可提升30%生成速度
  • 建议使用setFont()指定系统已安装的中文字体
  • 汉字数量不宜超过5个以免影响用户体验

3.3 GIF动态验证码(GifCaptcha)

动态效果可有效对抗截图识别:

@GetMapping("/gif") public void gifCaptcha(HttpServletRequest request, HttpServletResponse response) { GifCaptcha captcha = new GifCaptcha(130, 48); captcha.setLen(5); request.getSession().setAttribute("captcha", captcha.text()); captcha.out(response.getOutputStream()); }

性能注意

  • 生成耗时是静态验证码的2-3倍
  • 生产环境建议添加缓存策略
  • 帧数可通过setQuality(10)调整(默认15)

3.4 特殊字符验证码(SpecCaptcha)

包含大小写字母+数字的基础验证码:

@GetMapping("/spec") public void specCaptcha(HttpServletRequest request, HttpServletResponse response) { SpecCaptcha captcha = new SpecCaptcha(130, 48, 5); request.getSession().setAttribute("captcha", captcha.text()); captcha.out(response.getOutputStream()); }

安全增强

// 在配置中增加干扰元素 captcha.setCharType(Captcha.TYPE_ONLY_NUMBER); // 纯数字 captcha.setCharType(Captcha.TYPE_ONLY_UPPER); // 仅大写字母

3.5 自定义混合验证码(HybridCaptcha)

组合多种样式实现更高安全性:

@GetMapping("/hybrid") public void hybridCaptcha(HttpServletRequest request, HttpServletResponse response) { Random random = new Random(); int type = random.nextInt(4); Captcha captcha; switch(type) { case 0: captcha = new ArithmeticCaptcha(130, 48); break; case 1: captcha = new ChineseCaptcha(130, 48); break; case 2: captcha = new GifCaptcha(130, 48); break; default: captcha = new SpecCaptcha(130, 48, 5); } request.getSession().setAttribute("captcha", captcha.text()); captcha.out(response.getOutputStream()); }

4. 生产环境实战技巧

4.1 性能优化方案

对象池技术

private static final GenericObjectPool<SpecCaptcha> captchaPool = new GenericObjectPool<>(new BasePooledObjectFactory<>() { @Override public SpecCaptcha create() { return new SpecCaptcha(130, 48, 5); } @Override public PooledObject<SpecCaptcha> wrap(SpecCaptcha obj) { return new DefaultPooledObject<>(obj); } }); static { captchaPool.setMaxTotal(20); // 根据QPS调整 }

Redis缓存验证码

@GetMapping("/captcha") public void getCaptcha(HttpServletResponse response) { SpecCaptcha captcha = new SpecCaptcha(130, 48, 5); String code = captcha.text(); String uuid = UUID.randomUUID().toString(); redisTemplate.opsForValue().set("captcha:"+uuid, code, 5, TimeUnit.MINUTES); // 返回uuid和图片 Map<String,Object> result = new HashMap<>(); result.put("uuid", uuid); result.put("img", captcha.toBase64()); return result; }

4.2 安全增强措施

频率限制实现

@RateLimiter(value = 5, key = "#request.getRemoteAddr()") @GetMapping("/captcha") public void getCaptcha(HttpServletRequest request, HttpServletResponse response) { // 生成逻辑 }

验证码验证服务

public boolean validate(String uuid, String inputCode) { String redisKey = "captcha:" + uuid; String realCode = redisTemplate.opsForValue().get(redisKey); redisTemplate.delete(redisKey); if(StringUtils.isBlank(inputCode) || !inputCode.equalsIgnoreCase(realCode)) { return false; } return true; }

5. 常见问题排查指南

5.1 字体显示异常解决方案

Linux环境字体缺失

# 查看系统字体 fc-list # 安装中文字体(以宋体为例) sudo apt install fonts-wqy-microhei

Java代码中指定字体路径:

Font font = Font.createFont(Font.TRUETYPE_FONT, new File("/usr/share/fonts/wqy-microhei.ttc")); GraphicsEnvironment.getLocalGraphicsEnvironment() .registerFont(font);

5.2 高并发场景问题

Session冲突处理

@GetMapping("/captcha") public void getCaptcha(@RequestParam String token, HttpServletResponse response) { // 使用客户端传入的token作为key SpecCaptcha captcha = new SpecCaptcha(130, 48, 5); redisTemplate.opsForValue().set("captcha:"+token, captcha.text(), 2, TimeUnit.MINUTES); captcha.out(response.getOutputStream()); }

内存泄漏预防

// 在过滤器或拦截器中添加清理逻辑 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { request.getSession().removeAttribute("captcha"); }

5.3 移动端适配技巧

响应式尺寸设置

@GetMapping("/captcha") public void getCaptcha(@RequestParam(defaultValue = "130") int width, @RequestParam(defaultValue = "48") int height, HttpServletResponse response) { // 根据设备类型动态调整尺寸 if(isMobileDevice(request)) { width = (int)(width * 1.5); height = (int)(height * 1.5); } SpecCaptcha captcha = new SpecCaptcha(width, height, 5); // ... }

前端调用示例

function refreshCaptcha() { const width = window.innerWidth > 768 ? 130 : 200; const height = window.innerWidth > 768 ? 48 : 80; fetch(`/captcha?width=${width}&height=${height}&t=${Date.now()}`) .then(response => { document.getElementById('captchaImg').src = URL.createObjectURL(response.blob()); }); }

6. 进阶扩展方案

6.1 行为验证码集成

结合滑动验证等增强方案:

@GetMapping("/slide") public SlideResult slideCaptcha() { SlideCaptcha captcha = new SlideCaptcha(); // 生成滑块位置等数据 SlideResult result = captcha.generate(); redisTemplate.opsForValue().set("slide:"+result.getToken(), result.getPositionX(), 5, TimeUnit.MINUTES); return result; } @PostMapping("/verifySlide") public boolean verifySlide(@RequestBody SlideVerifyRequest request) { Integer realX = redisTemplate.opsForValue().get("slide:"+request.getToken()); return Math.abs(request.getMoveX() - realX) < 5; }

6.2 验证码数据分析

收集验证码使用数据优化策略:

@Aspect @Component public class CaptchaMonitor { @Autowired private RedisTemplate<String, Object> redisTemplate; @AfterReturning(pointcut = "execution(* com..captcha.*Controller.*(..))") public void recordUsage(JoinPoint jp) { String method = jp.getSignature().getName(); redisTemplate.opsForValue().increment("captcha:stats:"+method+":count"); } @AfterThrowing(pointcut = "execution(* com..captcha.*Controller.*(..))", throwing = "ex") public void recordError(JoinPoint jp, Exception ex) { String method = jp.getSignature().getName(); redisTemplate.opsForValue().increment("captcha:stats:"+method+":error"); } }

在实际项目中,我建议根据具体场景选择验证码类型。对于后台管理系统,算术验证码已经足够;对于高安全要求的金融场景,可以结合GIF动态验证码+行为验证实现多重防护。EasyCaptcha的轻量级特性使其非常适合快速集成,但需要注意在高并发场景下做好性能优化。

← 返回列表