本文详细讲解 SpringBoot 项目中图形验证码的两种实现方案,包括手写自定义验证码工具类和基于 Hutool 工具库快速集成线段、圆形、扭曲、GIF 四种验证码,附带完整代码示例与接口测试步骤,帮助开发者解决登录、注册等场景的人机验证需求。
一、为什么需要图形验证码?
在登录、注册、密码重置等用户交互场景中,图形验证码是防御恶意脚本、暴力破解的重要手段。它通过将随机字符与干扰元素结合,确保操作由真实用户完成,而非自动化程序。

传统实现方式有两种:一是手动编写验证码生成逻辑,二是使用成熟工具库快速集成。下面分别讲解这两种方案的具体操作,你可以根据项目需求选择合适的方式。
二、方案一:手写自定义验证码工具类
如果需要高度定制验证码样式(如特定字体、干扰线密度),可以手动开发工具类。以下是完整实现步骤。
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);
}
}

三、方案二:使用 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();
}

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

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

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

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

示例 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();
}

四、接口测试:用 Apifox 验证验证码
无论使用哪种方案,都需要测试接口是否正常返回验证码。以 Apifox 为例,步骤如下:
- 启动 SpringBoot 项目,确保接口地址正确(如
http://localhost:8080/checkCode/line)。 - 打开 Apifox,新建“GET”请求,输入接口地址。
- 点击“发送”按钮,若响应体显示图片,说明接口正常;若显示乱码,检查
response.setContentType("image/jpeg")是否配置。 - 多次发送请求,观察验证码是否变化(确保缓存已禁用)。
五、注意事项
- Session 存储与验证:生成验证码时需将字符(或运算结果)存入 Session,用户提交表单后,需对比表单输入与 Session 中的值,验证通过后清除 Session(避免重复使用)。
- 图片尺寸适配:根据前端页面布局调整验证码宽高,建议宽度不小于 120px,高度不小于 30px,保证用户可清晰识别。
- 依赖版本兼容:Hutool 不同版本的 API 可能存在差异,若出现报错,可参考官网文档调整版本或代码。
- 安全性升级:对于高安全需求场景,可结合 IP 限制(同一 IP 频繁获取验证码限流)、短信验证等方式,进一步提升防御能力。