java中基于 Hutool 工具库实现的图形验证码工具类
一、介绍
基于 Hutool 工具库实现的图形验证码工具类 CaptchaUtil,适配 Spring 生态,提供生成图形验证码(Base64 / 流)+ 验证码存储(Session/Redis)+ 输入校验的完整能力,支持登录、注册等场景的人机校验。
注:Spring 生态 = 核心 Spring 框架 + 一系列配套组件 / 子项目 + 第三方集成方案
二、实现
1.引入的依赖
需在 Maven/Gradle 中引入 Hutool 核心包(图形验证码依赖 hutool-captcha 模块,已包含在核心包中),若使用 Redis 存储需额外引入 Spring Data Redis:
xml
<!-- Hutool 核心包(含图形验证码功能) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.28</version> <!-- 推荐使用最新稳定版 -->
</dependency>
<!-- Spring Web(用于 HttpServletRequest/Response、Session) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version> <!-- 与项目 Spring 版本一致 -->
</dependency>
<!-- 可选:Redis 存储(分布式环境推荐) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
2.实现工具类
支持 2 种存储策略(Session 单机版 / Redis 分布式版)、3 种验证码类型(算术验证码、字符验证码、中文验证码),生成结果支持 Base64 编码(前端直接渲染)或字节流(直接响应给浏览器)。
java
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.ChineseCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ArithmeticCaptcha;
import cn.hutool.captcha.Captcha;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* 基于 Hutool 的图形验证码工具类(适配 Spring 生态)
* 支持:生成验证码(Base64/流)、存储验证码(Session/Redis)、校验验证码
*/
public class CaptchaUtil {
// -------------------------- 配置常量(可根据项目需求调整) --------------------------
/** 验证码默认宽度 */
private static final int DEFAULT_WIDTH = 120;
/** 验证码默认高度 */
private static final int DEFAULT_HEIGHT = 40;
/** 字符验证码默认长度 */
private static final int DEFAULT_CHAR_LENGTH = 4;
/** 验证码默认过期时间(秒) */
private static final int DEFAULT_EXPIRE_SECONDS = 180;
/** Session 中验证码存储的 key */
private static final String SESSION_CAPTCHA_KEY = "SESSION_CAPTCHA_CODE";
/** Redis 中验证码存储的前缀(分布式环境) */
private static final String REDIS_CAPTCHA_PREFIX = "captcha:";
// -------------------------- 存储策略枚举 --------------------------
public enum StoreType {
/** 本地 Session 存储(单机部署) */
SESSION,
/** Redis 存储(分布式部署) */
REDIS
}
// -------------------------- 验证码类型枚举 --------------------------
public enum CaptchaType {
/** 算术验证码(如:3+5=?) */
ARITHMETIC,
/** 字符验证码(字母+数字) */
CHAR,
/** 中文验证码(中文汉字) */
CHINESE
}
// -------------------------- 核心生成方法 --------------------------
/**
* 生成图形验证码(返回 Base64 编码,前端可直接用 <img src="data:image/png;base64,..." /> 渲染)
* @param captchaType 验证码类型
* @param storeType 存储策略
* @return 验证码信息(Base64 编码 + 验证码标识,Redis 存储时需返回标识给前端)
*/
public static CaptchaResult generateBase64(CaptchaType captchaType, StoreType storeType) {
// 1. 创建对应类型的验证码对象
Captcha captcha = createCaptcha(captchaType);
// 2. 获取验证码内容(算术验证码为计算结果,其他为显示的字符)
String code = getCaptchaCode(captcha);
// 3. 存储验证码(Session/Redis)
String captchaKey = storeCaptcha(code, storeType);
// 4. 将验证码图片转为 Base64 编码
String base64 = imageToBase64(captcha);
// 5. 返回结果(Redis 需携带 captchaKey 供校验,Session 无需)
return new CaptchaResult(base64, captchaKey);
}
/**
* 生成图形验证码(直接响应字节流到浏览器,前端用 <img src="/captcha" /> 调用)
* @param captchaType 验证码类型
* @param storeType 存储策略
* @throws IOException 流写入异常
*/
public static void generateStream(CaptchaType captchaType, StoreType storeType) throws IOException {
// 1. 创建对应类型的验证码对象
Captcha captcha = createCaptcha(captchaType);
// 2. 获取验证码内容并存储
String code = getCaptchaCode(captcha);
storeCaptcha(code, storeType);
// 3. 直接响应到浏览器(设置响应头为图片类型)
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
captcha.write(attributes.getResponse().getOutputStream());
}
}
// -------------------------- 核心校验方法 --------------------------
/**
* 校验用户输入的验证码是否正确
* @param userInputCode 用户输入的验证码
* @param storeType 存储策略(需与生成时一致)
* @param captchaKey Redis 存储时需传入前端返回的标识(Session 存储时可传 null)
* @return true=校验通过,false=校验失败
*/
public static boolean validate(String userInputCode, StoreType storeType, String captchaKey) {
// 1. 参数校验
if (StrUtil.isBlank(userInputCode)) {
return false;
}
// 2. 获取存储的验证码
String storedCode = getStoredCaptcha(storeType, captchaKey);
if (StrUtil.isBlank(storedCode)) {
return false; // 验证码已过期或不存在
}
// 3. 忽略大小写校验(字符/中文验证码),算术验证码严格匹配
boolean isMatch = storedCode.equalsIgnoreCase(userInputCode);
// 4. 校验通过后删除验证码(防止重复使用)
if (isMatch) {
removeCaptcha(storeType, captchaKey);
}
return isMatch;
}
// -------------------------- 内部辅助方法 --------------------------
/**
* 创建指定类型的验证码对象
*/
private static Captcha createCaptcha(CaptchaType captchaType) {
switch (captchaType) {
case ARITHMETIC:
// 算术验证码(位数默认 2 位,可调整)
return new ArithmeticCaptcha(DEFAULT_WIDTH, DEFAULT_HEIGHT, 2);
case CHAR:
// 字符验证码(干扰线+圆圈干扰)
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_CHAR_LENGTH, 10);
lineCaptcha.setGenerator(new RandomGenerator("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", DEFAULT_CHAR_LENGTH));
return lineCaptcha;
case CHINESE:
// 中文验证码
return CaptchaUtil.createChineseCaptcha(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_CHAR_LENGTH);
default:
throw new IllegalArgumentException("不支持的验证码类型:" + captchaType);
}
}
/**
* 获取验证码的核心内容(算术验证码取结果,其他取显示字符)
*/
private static String getCaptchaCode(Captcha captcha) {
if (captcha instanceof ArithmeticCaptcha) {
// 算术验证码的 code 是计算表达式(如 "3+5="),结果存在 arithmeticResult 中
return String.valueOf(((ArithmeticCaptcha) captcha).getArithmeticResult());
} else {
// 字符/中文验证码的 code 就是显示的内容
return captcha.getCode();
}
}
/**
* 存储验证码(Session/Redis)
* @return 存储的 key(Redis 需返回给前端,Session 直接用固定 key)
*/
private static String storeCaptcha(String code, StoreType storeType) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new IllegalStateException("当前环境不存在 HttpServletRequest");
}
switch (storeType) {
case SESSION:
// Session 存储(单机部署)
HttpSession session = attributes.getRequest().getSession();
session.setAttribute(SESSION_CAPTCHA_KEY, code);
session.setMaxInactiveInterval(DEFAULT_EXPIRE_SECONDS); // 设置过期时间
return SESSION_CAPTCHA_KEY;
case REDIS:
// Redis 存储(分布式部署):生成唯一标识作为 key
String captchaKey = REDIS_CAPTCHA_PREFIX + IdUtil.fastSimpleUUID();
StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
redisTemplate.opsForValue().set(captchaKey, code, DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS);
return captchaKey;
default:
throw new IllegalArgumentException("不支持的存储策略:" + storeType);
}
}
/**
* 获取存储的验证码
*/
private static String getStoredCaptcha(StoreType storeType, String captchaKey) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return null;
}
switch (storeType) {
case SESSION:
HttpSession session = attributes.getRequest().getSession(false);
return session != null ? (String) session.getAttribute(SESSION_CAPTCHA_KEY) : null;
case REDIS:
if (StrUtil.isBlank(captchaKey) || !captchaKey.startsWith(REDIS_CAPTCHA_PREFIX)) {
return null;
}
StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
return redisTemplate.opsForValue().get(captchaKey);
default:
return null;
}
}
/**
* 删除已校验通过的验证码(防止重复使用)
*/
private static void removeCaptcha(StoreType storeType, String captchaKey) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
switch (storeType) {
case SESSION:
HttpSession session = attributes.getRequest().getSession(false);
if (session != null) {
session.removeAttribute(SESSION_CAPTCHA_KEY);
}
break;
case REDIS:
if (StrUtil.isNotBlank(captchaKey)) {
StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
redisTemplate.delete(captchaKey);
}
break;
}
}
/**
* 验证码图片转为 Base64 编码(带 DataURI 前缀,前端可直接使用)
*/
private static String imageToBase64(Captcha captcha) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
captcha.write(outputStream);
byte[] imageBytes = outputStream.toByteArray();
// 拼接 DataURI 前缀(png 格式)
return "data:image/png;base64," + Base64.encode(imageBytes);
} catch (IOException e) {
throw new RuntimeException("验证码图片转 Base64 失败", e);
}
}
// -------------------------- 内部结果封装类 --------------------------
/**
* 验证码生成结果(Base64 编码 + 验证码标识)
*/
public static class CaptchaResult {
/** 验证码 Base64 编码(前端可直接渲染) */
private final String base64;
/** 验证码标识(Redis 存储时需返回给前端,校验时传入) */
private final String captchaKey;
public CaptchaResult(String base64, String captchaKey) {
this.base64 = base64;
this.captchaKey = captchaKey;
}
// getter
public String getBase64() {
return base64;
}
public String getCaptchaKey() {
return captchaKey;
}
}
}
三、使用
1. 配置 SpringUtil(Hutool 提供,用于获取 Spring Bean)
需在 Spring 配置类中启用 Hutool 的 SpringUtil,用于工具类中获取 StringRedisTemplate:
java
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HutoolConfig {
@Bean
public SpringUtil springUtil() {
return new SpringUtil();
}
}
2.Controller 层调用示例
java
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
/**
* 生成 Base64 验证码(适合前后端分离项目)
* 前端请求后,用 <img src="${base64}" /> 渲染,校验时传入 captchaKey 和用户输入
*/
@GetMapping("/base64")
public CaptchaUtil.CaptchaResult generateBase64Captcha() {
// 生成算术验证码,使用 Redis 存储(分布式环境)
return CaptchaUtil.generateBase64(CaptchaUtil.CaptchaType.ARITHMETIC, CaptchaUtil.StoreType.REDIS);
}
/**
* 生成流验证码(适合传统 JSP/Thymeleaf 项目)
* 前端用 <img src="/captcha/stream" onclick="this.src='/captcha/stream?'+Math.random()" /> 渲染
*/
@GetMapping("/stream")
public void generateStreamCaptcha(HttpServletResponse response) throws IOException {
// 生成字符验证码,使用 Session 存储(单机环境)
response.setContentType("image/png"); // 设置响应类型为图片
response.setHeader("Pragma", "no-cache"); // 禁止缓存
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
CaptchaUtil.generateStream(CaptchaUtil.CaptchaType.CHAR, CaptchaUtil.StoreType.SESSION);
}
/**
* 校验验证码
* @param userInput 用户输入的验证码
* @param captchaKey Redis 存储时的验证码标识(Session 存储时可传 null)
*/
@PostMapping("/validate")
public boolean validateCaptcha(@RequestParam String userInput, @RequestParam(required = false) String captchaKey) {
// 若生成时用 Redis 存储,这里 storeType 传 REDIS,否则传 SESSION
return CaptchaUtil.validate(userInput, CaptchaUtil.StoreType.REDIS, captchaKey);
}
}
3. 前端使用示例(Vue 为例)
vue
<template>
<div>
<!-- 渲染 Base64 验证码 -->
<img :src="captchaBase64" @click="refreshCaptcha" style="cursor: pointer;" />
<input v-model="userInput" placeholder="请输入验证码" />
<button @click="validate">校验</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
captchaBase64: '',
captchaKey: '', // Redis 存储的验证码标识
userInput: ''
};
},
mounted() {
this.refreshCaptcha(); // 初始化加载验证码
},
methods: {
// 刷新验证码
async refreshCaptcha() {
const res = await axios.get('/captcha/base64');
this.captchaBase64 = res.data.base64;
this.captchaKey = res.data.captchaKey;
},
// 校验验证码
async validate() {
const res = await axios.post('/captcha/validate', {}, {
params: {
userInput: this.userInput,
captchaKey: this.captchaKey
}
});
if (res.data) {
alert('校验通过!');
} else {
alert('验证码错误或已过期!');
this.refreshCaptcha(); // 校验失败刷新验证码
}
}
}
};
</script>
四、特性说明
- 多类型支持:算术验证码(防机器人破解更强)、字符验证码、中文验证码,可按需选择。
- 多存储策略:
- Session 存储:适合单机部署,无需额外依赖。
- Redis 存储:适合分布式部署,通过唯一标识关联验证码,支持集群共享。
- 多返回格式:
- Base64 编码:适合前后端分离项目,前端直接渲染,无需额外请求。
- 字节流:适合传统服务端渲染项目,通过
<img src="/captcha/stream" />直接调用。
- 安全特性:
- 验证码过期时间可配置(默认 3 分钟)。
- 校验通过后自动删除验证码,防止重复使用。
- 字符验证码支持大小写忽略校验,算术验证码严格匹配。
五、扩展与调整
1.配置调整 :可修改工具类中的常量(宽度、高度、过期时间等),或新增配置类通过 @Value 注入(更灵活)。
2.验证码样式 :Hutool 支持自定义验证码样式(如干扰线数量、字体、颜色),可在 createCaptcha 方法中扩展,例如:
java
// 自定义字符验证码字体
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(120, 40, 4, 10);
lineCaptcha.setFont(new Font("微软雅黑", Font.BOLD, 20)); // 设置字体
lineCaptcha.setBackColor(Color.WHITE); // 设置背景色
3.分布式部署:若项目为分布式,就可以使用 Redis 存储策略,确保多实例共享验证码。
4.异常处理 :可在工具类中添加自定义异常(如 CaptchaExpiredException、CaptchaInvalidException),替代默认的 RuntimeException,便于业务层统一处理。
六、补充
1.在分布式系统中也可以使用别的分布式缓存中间件来替代Redis,例如:
- Memcached:轻量级分布式缓存,支持键值存储 + 过期时间,适合简单场景(仅存验证码 + TTL)。
- Redisson :基于 Redis 的分布式工具集,兼容 Redis API,还提供分布式锁、原子操作等增强功能(若项目已用 Redisson,可直接替换
StringRedisTemplate)。 - Tair/ Codis:阿里 / 字节开源的分布式缓存,兼容 Redis 协议,适合大规模集群场景。
2.Hutool还有什么常用工具?
1.StrUtil(字符串工具)
示例:
java
import cn.hutool.core.util.StrUtil;
public class StrUtilDemo {
public static void main(String[] args) {
String str = " Hello Hutool! ";
// 1. 空判断(避免 NPE,比 String.isEmpty() 更全面)
StrUtil.isEmpty(str); // false(有空格)
StrUtil.isBlank(str); // false(空格不算"有效字符")
StrUtil.isNotBlank(str); // true
// 2. 去除空格(灵活处理前后/中间空格)
StrUtil.trim(str); // "Hello Hutool!"(去除前后空格)
StrUtil.trimAll(str); // "HelloHutool!"(去除所有空格)
// 3. 字符串截取(安全截取,避免索引越界)
StrUtil.sub(str, 2, 8); // "Hello "(从索引2到8,左闭右开)
StrUtil.subAfter(str, "Hello", false); // " Hutool! "(截取"Hello"之后的内容)
// 4. 字符串拼接(比 + 号高效,支持多参数)
StrUtil.format("姓名:{},年龄:{}", "张三", 25); // "姓名:张三,年龄:25"
StrUtil.join(",", "A", "B", "C"); // "A,B,C"
// 5. 格式校验(无需正则功底)
StrUtil.isEmail("test@163.com"); // true
StrUtil.isMobile("13800138000"); // true(支持国内手机号校验)
StrUtil.isNumber("123456"); // true(判断是否为纯数字字符串)
}
}
2.DateUtil(日期时间工具)
示例:
java
/*
优势:
线程安全(内部使用 ThreadLocal 管理格式化器);
支持 50+ 种常见日期格式自动识别;
日期计算 API 直观(无需手动处理毫秒数)。
*/
import cn.hutool.core.date.DateUtil;
import java.util.Date;
public class DateUtilDemo {
public static void main(String[] args) {
// 1. 获取当前时间(多种格式)
Date now = DateUtil.date(); // 当前 Date 对象
String nowStr = DateUtil.now(); // "2025-11-26 15:30:00"(yyyy-MM-dd HH:mm:ss)
String today = DateUtil.today(); // "2025-11-26"(yyyy-MM-dd)
// 2. 日期格式化(无需手动创建 SimpleDateFormat)
String format = DateUtil.format(now, "yyyy年MM月dd日 HH时mm分ss秒"); // "2025年11月26日 15时30分00秒"
// 3. 字符串转日期(自动识别常见格式)
Date date1 = DateUtil.parse("2025-11-26");
Date date2 = DateUtil.parse("2025/11/26 15:30");
Date date3 = DateUtil.parse("2025年11月26日", "yyyy年MM月dd日"); // 自定义格式
// 4. 日期计算(加减、相差天数/小时等)
Date tomorrow = DateUtil.offsetDay(now, 1); // 明天此时
Date lastWeek = DateUtil.offsetWeek(now, -1); // 上周此时
long days = DateUtil.between(date1, date2, DateUnit.DAY); // 两个日期相差的天数
boolean isLeapYear = DateUtil.isLeapYear(2024); // 判断闰年(true)
// 5. 常用日期判断
DateUtil.isSameDay(date1, date2); // 判断是否同一天
DateUtil.isWeekend(now); // 判断是否周末
}
}
3.CollUtil(集合工具)
简化 List、Map、Set 的创建、遍历、筛选、转换,解决集合操作的重复代码。
示例:
java
/*
快速创建集合(一行代码替代多行 add);
安全操作(CollUtil.get 避免 Map 获取值时的 NullPointerException);
简化集合转换(无需手动遍历)。
*/
import cn.hutool.core.collection.CollUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CollUtilDemo {
public static void main(String[] args) {
// 1. 快速创建集合(无需 new ArrayList/HashMap)
List<String> list = CollUtil.newArrayList("A", "B", "C");
Map<String, Integer> map = CollUtil.newHashMap();
map = CollUtil.of(new String[][]{{"name", "张三"}, {"age", 25}}); // 数组转 Map
// 2. 集合判空(避免 NPE)
CollUtil.isEmpty(list); // false
CollUtil.isNotEmpty(list); // true
// 3. 集合操作(拼接、筛选、分组)
String joinStr = CollUtil.join(list, ","); // "A,B,C"(集合转字符串)
List<String> subList = CollUtil.sub(list, 0, 2); // [A,B](安全截取)
List<String> filterList = CollUtil.filter(list, s -> s.startsWith("A")); // 筛选以 A 开头的元素
// 4. Map 操作(快速获取值,避免 NPE)
Integer age = CollUtil.get(map, "age", 0); // 获取 key 为 age 的值,默认 0
Map<String, List<String>> groupMap = CollUtil.groupBy(list, s -> s.length() + ""); // 按字符串长度分组
// 5. 集合转换(List <-> 数组、Map <-> List)
String[] arr = CollUtil.toArray(list, String.class); // List 转数组
List<Map.Entry<String, Integer>> entryList = CollUtil.entryToList(map); // Map 转 Entry 列表
}
}
其他工具:
IO 与文件工具(FileUtil/IoUtil),替代 java.io 原生 API(繁琐、需手动关流),简化文件读写、复制、移动、压缩等操作
流工具(IoUtil),处理 InputStream/OutputStream/Reader/Writer,自动关流,避免资源泄露:
加密解密工具(SecureUtil),封装 MD5、SHA、AES、RSA 等常见加密算法,无需关注底层实现,一行代码完成加密。
网络工具(HttpUtil),替代 HttpClient(原生 API 繁琐),简化 HTTP 请求(GET/POST/ 文件上传下载),支持 HTTPS、超时设置、请求头自定义。
等等,就不一一介绍了。