SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

49次阅读
没有评论

本文详细讲解 SpringBoot 项目中图形验证码的两种实现方案,包括手写自定义验证码工具类和基于 Hutool 工具库快速集成线段、圆形、扭曲、GIF 四种验证码,附带完整代码示例与接口测试步骤,帮助开发者解决登录、注册等场景的人机验证需求。

一、为什么需要图形验证码?

在登录、注册、密码重置等用户交互场景中,图形验证码是防御恶意脚本、暴力破解的重要手段。它通过将随机字符与干扰元素结合,确保操作由真实用户完成,而非自动化程序。

SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

传统实现方式有两种:一是手动编写验证码生成逻辑,二是使用成熟工具库快速集成。下面分别讲解这两种方案的具体操作,你可以根据项目需求选择合适的方式。

二、方案一:手写自定义验证码工具类

如果需要高度定制验证码样式(如特定字体、干扰线密度),可以手动开发工具类。以下是完整实现步骤。

2.1 新建验证码工具类

在 SpringBoot 项目的 util 包下创建 Code 类,核心逻辑包括生成随机字符、绘制干扰线、输出图片到响应流,并将验证码存入 Session 用于后续验证。

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

public class Code {
    // 存储验证码到 Session 的 key
    public static final String RANDOMCODEKEY = "ValidateCode";
    // 随机数生成器
    private final Random random = new Random();
    // 验证码字符库(数字 + 大写字母)private final String randomString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    // 验证码图片宽高与干扰线配置
    private int width = 80;
    private int height = 26;
    private int lineSize = 40;
    private int stringNum = 4;

    /**
     * 获取验证码字体(固定为 Fixedsys,大小 18)*/
    private Font getFont() {return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
    }

    /**
     * 生成随机颜色(避免颜色过深或过浅)*/
    private Color getRandColor(int fc, int bc) {if (fc > 255) fc = 255;
        if (bc > 255) bc = 255;
        int r = fc + random.nextInt(bc - fc - 16);
        int g = fc + random.nextInt(bc - fc - 14);
        int b = fc + random.nextInt(bc - fc - 18);
        return new Color(r, g, b);
    }

    /**
     * 绘制干扰线(随机位置、随机长度)*/
    private void drawLine(Graphics g) {int x = random.nextInt(width);
        int y = random.nextInt(height);
        int xl = random.nextInt(13);
        int yl = random.nextInt(15);
        g.drawLine(x, y, x + xl, y + yl);
    }

    /**
     * 绘制随机字符(随机颜色、轻微偏移)*/
    private String drawString(Graphics g, String randomStr, int i) {g.setFont(getFont());
        g.setColor(new Color(random.nextInt(101), random.nextInt(111), random.nextInt(121)));
        String charStr = String.valueOf(randomString.charAt(random.nextInt(randomString.length())));
        randomStr += charStr;
        // 字符位置轻微偏移,增加识别难度
        g.translate(random.nextInt(3), random.nextInt(3));
        g.drawString(charStr, 13 * i, 16);
        return randomStr;
    }

    /**
     * 核心方法:生成验证码并输出到响应流
     */
    public void getValidateCode(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession();
        // 1. 创建图片缓冲区
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        Graphics g = image.getGraphics();

        // 2. 绘制图片背景
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
        g.setColor(getRandColor(110, 133));

        // 3. 绘制干扰线
        for (int i = 0; i <= lineSize; i++) {drawLine(g);
        }

        // 4. 绘制验证码字符
        String randomStr = "";
        for (int i = 1; i <= stringNum; i++) {randomStr = drawString(g, randomStr, i);
        }

        // 5. 存储验证码到 Session(覆盖旧值)session.removeAttribute(RANDOMCODEKEY);
        session.setAttribute(RANDOMCODEKEY, randomStr);

        // 6. 关闭资源并输出图片
        g.dispose();
        try {ImageIO.write(image, "JPEG", response.getOutputStream());
        } catch (Exception e) {e.printStackTrace();
        }
    }
}

2.2 在 Controller 中调用工具类

创建CaptchaController,定义接口/checkCode2,设置响应格式为图片,并禁用浏览器缓存(避免验证码重复加载)。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
public class CaptchaController {

    /**
     * 自定义验证码接口
     */
    @GetMapping("/checkCode2")
    public void checkCode2(HttpServletRequest request, HttpServletResponse response) {
        // 1. 设置响应格式为 JPEG 图片
        response.setContentType("image/jpeg");
        // 2. 禁用浏览器缓存(关键:避免验证码复用)response.setDateHeader("Expires", 0);
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        // 3. 调用工具类生成验证码
        Code code = new Code();
        code.getValidateCode(request, response);
    }
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

三、方案二:使用 Hutool 快速集成验证码

Hutool 是 Java 生态中常用的工具库,其 hutool-captcha 模块已封装好四种验证码,无需重复开发,推荐项目中优先使用。

3.1 引入 Hutool 依赖

pom.xml 中添加依赖(Maven),如果是 Gradle 项目,可参考 Hutool 官网 调整配置。

<!-- 方式 1:仅引入图形验证码模块(轻量)-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.8.6</version>
</dependency>

<!-- 方式 2:引入 Hutool 所有模块(适合需其他工具类的场景)-->
<!--
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>
-->

3.2 四种验证码实现示例

Hutool 提供LineCaptcha(线段干扰)、CircleCaptcha(圆形干扰)、ShearCaptcha(扭曲干扰)、GifCaptcha(动态 GIF),接口调用逻辑类似,仅需修改验证码创建方式。

3.2.1 线段干扰验证码(LineCaptcha)

最基础的验证码类型,通过线段干扰提高安全性。

@GetMapping("/checkCode/line")
public void lineCaptcha(HttpServletResponse response) throws IOException {
    // 1. 创建线段验证码:宽 130px、高 38px、字符数 5、干扰线数 5
    LineCaptcha captcha = CaptchaUtil.createLineCaptcha(130, 38, 5, 5);
    // 2. 禁用缓存(同自定义方案)response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    // 3. 输出验证码到响应流
    captcha.write(response.getOutputStream());
    // 4. 关闭流(避免资源泄漏)response.getOutputStream().close();
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

3.2.2 圆形干扰验证码(CircleCaptcha)

用圆形斑点替代线段,视觉效果更友好。

@GetMapping("/checkCode/circle")
public void circleCaptcha(HttpServletResponse response) throws IOException {
    // 创建圆形验证码:宽 130px、高 38px、字符数 5、干扰圆数 20
    CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(130, 38, 5, 20);
    // 后续禁用缓存、输出流逻辑与线段验证码一致
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

3.2.3 扭曲干扰验证码(ShearCaptcha)

字符扭曲变形,安全性更高,适合对验证强度要求高的场景。

@GetMapping("/checkCode/shear")
public void shearCaptcha(HttpServletResponse response) throws IOException {
    // 创建扭曲验证码:宽 130px、高 38px、字符数 5、干扰线数 5
    ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(130, 38, 5, 5);
    // 禁用缓存与输出逻辑同上
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

3.2.4 动态 GIF 验证码(GifCaptcha)

动态图片验证码,能有效防御简单的图像识别脚本。

@GetMapping("/checkCode/gif")
public void gifCaptcha(HttpServletResponse response) throws IOException {
    // 创建 GIF 验证码:宽 130px、高 38px、字符数 5(无干扰线参数)GifCaptcha captcha = CaptchaUtil.createGifCaptcha(130, 38, 5);
    // 注意:GIF 验证码响应类型仍为 image/jpeg,浏览器可自动识别
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

3.3 自定义验证码内容

Hutool 支持自定义验证码字符库,例如纯数字、纯字母,甚至四则运算验证码。

示例 1:纯数字验证码

@GetMapping("/checkCode/number")
public void numberCaptcha(HttpServletResponse response) throws IOException {
    // 1. 自定义字符生成器:仅使用 0 -9,生成 4 位字符
    RandomGenerator numberGenerator = new RandomGenerator("0123456789", 4);
    // 2. 创建线段验证码并设置自定义生成器
    LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100);
    captcha.setGenerator(numberGenerator);
    // 3. 重新生成验证码(必须调用,否则仍使用默认字符)captcha.createCode();
    // 4. 输出验证码
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

示例 2:四则运算验证码

通过 MathGenerator 生成“3+5=?”这类运算式,用户需输入结果作为验证(需注意:Session 中存储的是运算结果,而非显示的字符)。

@GetMapping("/checkCode/math")
public void mathCaptcha(HttpServletResponse response, HttpSession session) throws IOException {
    // 1. 创建扭曲验证码
    ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
    // 2. 设置运算式生成器
    captcha.setGenerator(new MathGenerator());
    // 3. 重新生成验证码
    captcha.createCode();
    // 4. 存储运算结果到 Session(后续验证需对比用户输入与该值)session.setAttribute("MathCode", captcha.getCode());
    // 5. 输出验证码
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
SpringBoot 图形验证码实现教程:从自定义开发到 Hutool 工具类实战

四、接口测试:用 Apifox 验证验证码

无论使用哪种方案,都需要测试接口是否正常返回验证码。以 Apifox 为例,步骤如下:

  1. 启动 SpringBoot 项目,确保接口地址正确(如http://localhost:8080/checkCode/line)。
  2. 打开 Apifox,新建“GET”请求,输入接口地址。
  3. 点击“发送”按钮,若响应体显示图片,说明接口正常;若显示乱码,检查 response.setContentType("image/jpeg") 是否配置。
  4. 多次发送请求,观察验证码是否变化(确保缓存已禁用)。

五、注意事项

  1. Session 存储与验证:生成验证码时需将字符(或运算结果)存入 Session,用户提交表单后,需对比表单输入与 Session 中的值,验证通过后清除 Session(避免重复使用)。
  2. 图片尺寸适配:根据前端页面布局调整验证码宽高,建议宽度不小于 120px,高度不小于 30px,保证用户可清晰识别。
  3. 依赖版本兼容:Hutool 不同版本的 API 可能存在差异,若出现报错,可参考官网文档调整版本或代码。
  4. 安全性升级:对于高安全需求场景,可结合 IP 限制(同一 IP 频繁获取验证码限流)、短信验证等方式,进一步提升防御能力。
正文完
 0
Fr2ed0m
版权声明:本站原创文章,由 Fr2ed0m 于2025-10-29发表,共计7861字。
转载说明:Unless otherwise specified, all articles are published by cc-4.0 protocol. Please indicate the source of reprint.
评论(没有评论)