告别踩坑!若依图片验证码实现与改造指南

简介: 若依(RuoYi)框架中图片验证码的完整实现逻辑、核心代码和定制化方法,我会从实现原理、核心代码拆解、关键配置、定制化改造四个维度讲解,覆盖验证码生成、存储、校验全流程,让你既能理解原理,也能按需修改验证码样式 / 规则。

 若依(RuoYi)框架中图片验证码的完整实现逻辑、核心代码和定制化方法,我会从实现原理、核心代码拆解、关键配置、定制化改造四个维度讲解,覆盖验证码生成、存储、校验全流程,让你既能理解原理,也能按需修改验证码样式 / 规则。

若依最新前后端分离源码下载:

https://gitee.com/ruoyieleadmin/ruoyi-ele-admin

一、图片验证码核心实现原理

若依框架的图片验证码基于 Java 图形绘制 + Redis 存储 + 前端交互 实现,核心流程如下:

image.gif 编辑

核心关键点:

  • 验证码字符不直接返回前端,仅存储在 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
    }
}

image.gif

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("验证码生成失败");
        }
    }
}

image.gif

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;
}

image.gif

三、关键配置与注意事项

1. 核心配置(无需额外配置,若依默认支持)

  • Redis 配置:验证码依赖 Redis 存储,需确保 Redis 服务正常,配置正确(参考之前 Redis 配置章节);
  • 跨域配置:若前端与后端跨域,需在 WebMvcConfig 中放行 /captchaImage 接口;
  • 接口权限:/captchaImage 接口需加入 Shiro/Spring Security 白名单,无需登录即可访问: java
// ShiroConfig.java 中添加
filterChainDefinitionMap.put("/captchaImage", "anon");
  • image.gif

2. 安全性注意事项

  • 字符集:若依已排除易混淆字符(0/O、1/I),避免用户识别错误;
  • 过期时间:默认 2 分钟,不可过长(易被破解),也不可过短(用户来不及输入);
  • 防刷新:可添加接口限流(参考 Redis 限流章节),限制 /captchaImage 接口的请求频率,避免恶意刷取验证码;
  • 大小写:校验时忽略大小写(equalsIgnoreCase),提升用户体验。

四、定制化改造(按需调整)

1. 修改验证码样式

  • 调整尺寸:修改 CaptchaUtilWIDTH(宽度)、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;
  • image.gif

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);

image.gif

3. 对接第三方验证码(如极验、阿里云验证码)

若需更高安全级别的验证码(滑块、点选),可替换原生实现:

  1. 引入第三方验证码 SDK(如极验:geetest-sdk-java);
  2. 修改 CaptchaController,调用第三方接口生成验证码参数;
  3. 登录接口中调用第三方校验接口,替换 Redis 校验逻辑。

五、常见问题及解决方法

1. 验证码图片无法显示

  • 原因:图片 Base64 编码错误、跨域拦截、Redis 未启动;
  • 解决:
  1. 检查 Redis 服务是否正常,验证码 UUID 是否存入 Redis;
  2. 检查前端 <img> 标签的 src 是否正确(格式:data:image/jpeg;base64,${imgBase64});
  3. 确认 /captchaImage 接口已加入跨域 / 权限白名单。

2. 验证码校验一直失败

  • 原因:大小写未忽略、Redis 中验证码已过期、UUID 传递错误;
  • 解决:
  1. 校验时使用 equalsIgnoreCase 忽略大小写;
  2. 检查 Redis 验证码过期时间,确保用户输入时未过期;
  3. 前端确认 UUID 和验证码是否正确传递到后端。

3. 验证码生成性能低

  • 原因:图形绘制耗时、无缓存;
  • 解决:
  1. 减少干扰线 / 噪点数量(降低绘制耗时);
  2. 对验证码图片进行缓存(非核心验证码场景);
  3. 异步生成验证码(高并发场景)。

总结

若依框架图片验证码的核心实现逻辑:

  1. 生成:通过 Java 图形 API 绘制带干扰的字符图片,生成随机字符;
  2. 存储:字符存入 Redis(UUID 为 key,设置短过期时间),图片转 Base64 返回前端;
  3. 校验:登录时通过 UUID 从 Redis 读取字符,与用户输入对比,校验后删除;
  4. 定制:可调整样式、切换验证码类型,或对接第三方高安全验证码。

核心优化点:生产环境需添加接口限流、防刷新机制,高并发场景可考虑异步生成或对接第三方验证码服务。

若依最新前后端分离源码下载:

https://gitee.com/ruoyieleadmin/ruoyi-ele-admin

相关文章
|
算法 前端开发 JavaScript
若依框架---数据转树状层级
若依框架---数据转树状层级
1324 0
|
前端开发 JavaScript Java
若依前后端部署之后验证码不显示
若依前后端部署之后验证码不显示
|
7月前
|
SQL 监控 druid
若依(RuoYi)框架 Druid 配置详解
核心说明:Druid 是阿里巴巴开源的高性能数据库连接池,兼具连接池管理、SQL监控、防SQL注入、日志记录等核心功能,若依(RuoYi)框架(含RuoYi-Vue、RuoYi-Cloud)已默认集成 Druid,核心配置集中在 application-druid.yml 文件,同时需配合 application.yml 进行环境关联,以下是完整配置解析、实操步骤及常见问题,适配若依框架全版本,兼顾入门使用与进阶优化。
1938 4
|
7月前
|
安全 Java API
一文吃透:若依框架静态资源防盗链从原理到代码
若依(RuoYi)框架的防盗链功能实现方式,我会从核心原理、代码实现、配置方式、关键细节四个维度拆解,兼顾原理理解和实操落地,适配若依单体版(RuoYi-Vue)和微服务版(RuoYi-Cloud)的通用逻辑
447 3
|
7月前
|
XML Java 应用服务中间件
一步到位!若依框架升级 Tomcat 最新版完整教程
若依(RuoYi)框架升级 Tomcat 到最新版本的完整流程,我会从升级前提、核心步骤、版本适配、避坑指南四个维度拆解,覆盖单体版(RuoYi-Vue)和微服务版(RuoYi-Cloud),确保升级后系统稳定运行
967 3
|
10月前
|
架构师 IDE Java
【Java架构师】Maven中lombok那点事
SpringBoot项目中Lombok需在maven-compiler-plugin中配置`annotationProcessorPaths`,确保编译期生成getter/setter等方法;而`excludes`则在打包时排除Lombok依赖,减小体积,因运行时已无需该库。
896 1
|
存储 前端开发 Java
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
本文介绍了使用Kaptcha插件在SpringBoot项目中实现验证码的生成和验证,包括后端生成验证码、前端展示以及通过session进行验证码校验的完整前后端代码和配置过程。
4235 1
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
|
SQL 前端开发 NoSQL
【若依】前后端分离版快速上手
【若依】前后端分离版快速上手
2778 1
|
机器学习/深度学习 算法 数据可视化
UniApp手机滑块验证组件代码生成器
UniApp手机滑块验证组件代码生成器
1598 1
|
监控 Java 调度
若依修改定时任务,定时任务在系统监控的定时任务当中,宕机情况都不会去管,涉及到定时任务
若依修改定时任务,定时任务在系统监控的定时任务当中,宕机情况都不会去管,涉及到定时任务

热门文章

最新文章