若依(RuoYi)框架中图片验证码的完整实现逻辑、核心代码和定制化方法,我会从实现原理、核心代码拆解、关键配置、定制化改造四个维度讲解,覆盖验证码生成、存储、校验全流程,让你既能理解原理,也能按需修改验证码样式 / 规则。
若依最新前后端分离源码下载:
https://gitee.com/ruoyieleadmin/ruoyi-ele-admin
一、图片验证码核心实现原理
若依框架的图片验证码基于 Java 图形绘制 + Redis 存储 + 前端交互 实现,核心流程如下:
编辑
核心关键点:
- 验证码字符不直接返回前端,仅存储在 Redis,避免前端破解;
- 验证码图片添加干扰线、噪点、扭曲变形,提升安全性;
- 验证码设置短过期时间(默认 2 分钟),避免复用。
二、核心代码拆解(若依源码级解析)
若依的验证码功能集中在 ruoyi-common 模块的 captcha 包下,核心类如下:
1. 验证码生成核心类(CaptchaUtil.java)
负责生成随机字符、绘制验证码图片,是核心工具类:
java
package com.ruoyi.common.utils.captcha; import java.awt.*; import java.awt.image.BufferedImage; import java.util.Random; /** * 若依框架验证码生成工具类 */ public class CaptchaUtil { // 验证码字符集(排除易混淆字符:0/O、1/I等) private static final char[] CODE_CHAR = { '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; // 验证码宽度/高度 private static final int WIDTH = 110; private static final int HEIGHT = 38; // 验证码字符个数 private static final int CODE_COUNT = 4; // 干扰线数量 private static final int LINE_COUNT = 150; /** * 生成验证码图片+字符 * @return CaptchaResult:包含验证码字符、图片BufferedImage */ public static CaptchaResult generate() { // 1. 创建图片缓冲区 BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); // 2. 设置背景色(白色) g.setColor(Color.WHITE); g.fillRect(0, 0, WIDTH, HEIGHT); // 设置字体(加粗、倾斜,增加识别难度) g.setFont(new Font("Arial", Font.BOLD | Font.ITALIC, 24)); // 3. 生成随机验证码字符 Random random = new Random(); StringBuilder code = new StringBuilder(); for (int i = 0; i < CODE_COUNT; i++) { char c = CODE_CHAR[random.nextInt(CODE_CHAR.length)]; code.append(c); // 绘制字符(随机颜色、位置、旋转角度) g.setColor(new Color(random.nextInt(200), random.nextInt(200), random.nextInt(200))); // 字符旋转(-30° ~ 30°) g.rotate(Math.toRadians(random.nextInt(60) - 30), 25 * i + 10, HEIGHT / 2); g.drawString(String.valueOf(c), 25 * i + 10, HEIGHT / 2); // 旋转复位 g.rotate(-Math.toRadians(random.nextInt(60) - 30), 25 * i + 10, HEIGHT / 2); } // 4. 绘制干扰线(随机颜色、位置) for (int i = 0; i < LINE_COUNT; i++) { g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255))); g.drawLine(random.nextInt(WIDTH), random.nextInt(HEIGHT), random.nextInt(WIDTH), random.nextInt(HEIGHT)); } // 5. 绘制噪点(增加干扰) for (int i = 0; i < WIDTH * HEIGHT / 20; i++) { g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255))); g.fillRect(random.nextInt(WIDTH), random.nextInt(HEIGHT), 1, 1); } // 6. 释放资源 g.dispose(); // 返回结果:验证码字符、图片 return new CaptchaResult(code.toString(), image); } /** * 验证码结果封装类 */ public static class CaptchaResult { private String code; // 验证码字符 private BufferedImage image; // 验证码图片 // 构造器、getter/setter public CaptchaResult(String code, BufferedImage image) { this.code = code; this.image = image; } // ... 省略getter/setter } }
2. 验证码接口实现(CaptchaController.java)
提供前端访问的验证码接口,生成图片并存储到 Redis:
java
package com.ruoyi.web.controller.common; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.captcha.CaptchaUtil; import com.ruoyi.common.utils.redis.RedisCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.imageio.ImageIO; import java.io.ByteArrayOutputStream; import java.util.Base64; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 若依验证码接口控制器 */ @RestController public class CaptchaController { @Autowired private RedisCache redisCache; /** * 获取验证码接口 * 前端访问:/captchaImage */ @GetMapping("/captchaImage") public AjaxResult getCaptchaImage() { // 1. 生成验证码字符+图片 CaptchaUtil.CaptchaResult captchaResult = CaptchaUtil.generate(); String captchaCode = captchaResult.getCode(); // 2. 生成验证码UUID(作为Redis的key) String uuid = UUID.randomUUID().toString().replace("-", ""); // 3. 存入Redis,设置过期时间(2分钟) redisCache.setCacheObject("captcha_codes:" + uuid, captchaCode, 2, TimeUnit.MINUTES); // 4. 将图片转为Base64编码(返回前端展示) try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { ImageIO.write(captchaResult.getImage(), "jpg", outputStream); String imageBase64 = Base64.getEncoder().encodeToString(outputStream.toByteArray()); // 5. 返回结果:UUID、Base64图片(前端用<img src="data:image/jpeg;base64,...">展示) return AjaxResult.success() .put("uuid", uuid) .put("img", imageBase64); } catch (Exception e) { return AjaxResult.error("验证码生成失败"); } } }
3. 验证码校验逻辑(登录接口示例)
用户登录时校验验证码,核心代码在 LoginController.java:
java
/** * 登录接口验证码校验 */ @PostMapping("/login") public AjaxResult login(@RequestBody LoginBody loginBody) { AjaxResult ajax = AjaxResult.success(); // 1. 获取前端传入的UUID和验证码 String uuid = loginBody.getUuid(); String code = loginBody.getCode(); // 2. 从Redis获取正确的验证码 String captchaCode = redisCache.getCacheObject("captcha_codes:" + uuid); // 3. 校验验证码(忽略大小写) if (captchaCode == null || !captchaCode.equalsIgnoreCase(code)) { return AjaxResult.error("验证码错误"); } // 4. 校验通过,删除Redis中的验证码(避免复用) redisCache.deleteObject("captcha_codes:" + uuid); // 5. 执行后续登录逻辑... return ajax; }
三、关键配置与注意事项
1. 核心配置(无需额外配置,若依默认支持)
- Redis 配置:验证码依赖 Redis 存储,需确保 Redis 服务正常,配置正确(参考之前 Redis 配置章节);
- 跨域配置:若前端与后端跨域,需在
WebMvcConfig中放行/captchaImage接口; - 接口权限:
/captchaImage接口需加入 Shiro/Spring Security 白名单,无需登录即可访问: java
// ShiroConfig.java 中添加 filterChainDefinitionMap.put("/captchaImage", "anon");
2. 安全性注意事项
- 字符集:若依已排除易混淆字符(0/O、1/I),避免用户识别错误;
- 过期时间:默认 2 分钟,不可过长(易被破解),也不可过短(用户来不及输入);
- 防刷新:可添加接口限流(参考 Redis 限流章节),限制
/captchaImage接口的请求频率,避免恶意刷取验证码; - 大小写:校验时忽略大小写(
equalsIgnoreCase),提升用户体验。
四、定制化改造(按需调整)
1. 修改验证码样式
- 调整尺寸:修改
CaptchaUtil中WIDTH(宽度)、HEIGHT(高度); - 字符个数:修改
CODE_COUNT(如改为 6 位); - 干扰强度:调整
LINE_COUNT(干扰线数量)、噪点数量; - 字体 / 颜色:修改字体样式、字符颜色、背景色;
- 示例:改为 6 位数字验证码(仅保留数字字符): java
// 替换字符集 private static final char[] CODE_CHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; // 字符个数改为6 private static final int CODE_COUNT = 6;
2. 切换验证码类型(如算术验证码、滑块验证码)
若依默认是字符验证码,可扩展为算术验证码(适合低安全场景):
java
// 算术验证码生成逻辑(替换CaptchaUtil.generate()中的字符生成部分) String num1 = String.valueOf(random.nextInt(100)); String num2 = String.valueOf(random.nextInt(100)); String operator = "+"; // 可随机+、-、× int result = Integer.parseInt(num1) + Integer.parseInt(num2); String code = num1 + operator + num2 + "=?"; // 验证码显示:99+88=? // 存储到Redis的是结果:187 redisCache.setCacheObject("captcha_codes:" + uuid, String.valueOf(result), 2, TimeUnit.MINUTES);
3. 对接第三方验证码(如极验、阿里云验证码)
若需更高安全级别的验证码(滑块、点选),可替换原生实现:
- 引入第三方验证码 SDK(如极验:
geetest-sdk-java); - 修改
CaptchaController,调用第三方接口生成验证码参数; - 登录接口中调用第三方校验接口,替换 Redis 校验逻辑。
五、常见问题及解决方法
1. 验证码图片无法显示
- 原因:图片 Base64 编码错误、跨域拦截、Redis 未启动;
- 解决:
- 检查 Redis 服务是否正常,验证码 UUID 是否存入 Redis;
- 检查前端
<img>标签的 src 是否正确(格式:data:image/jpeg;base64,${imgBase64}); - 确认
/captchaImage接口已加入跨域 / 权限白名单。
2. 验证码校验一直失败
- 原因:大小写未忽略、Redis 中验证码已过期、UUID 传递错误;
- 解决:
- 校验时使用
equalsIgnoreCase忽略大小写; - 检查 Redis 验证码过期时间,确保用户输入时未过期;
- 前端确认 UUID 和验证码是否正确传递到后端。
3. 验证码生成性能低
- 原因:图形绘制耗时、无缓存;
- 解决:
- 减少干扰线 / 噪点数量(降低绘制耗时);
- 对验证码图片进行缓存(非核心验证码场景);
- 异步生成验证码(高并发场景)。
总结
若依框架图片验证码的核心实现逻辑:
- 生成:通过 Java 图形 API 绘制带干扰的字符图片,生成随机字符;
- 存储:字符存入 Redis(UUID 为 key,设置短过期时间),图片转 Base64 返回前端;
- 校验:登录时通过 UUID 从 Redis 读取字符,与用户输入对比,校验后删除;
- 定制:可调整样式、切换验证码类型,或对接第三方高安全验证码。
核心优化点:生产环境需添加接口限流、防刷新机制,高并发场景可考虑异步生成或对接第三方验证码服务。