行业知识
Java 验证码生成的两种方式【开箱即用】
2026-11-06 18:09:52
来源:互联网转载
小伙伴们好,���迎关注,一起学习,无限进步
()文章目录
- 通用公共配置
- 公共依赖
- 配置 `application.yml` 文件
- 公共常量
- Redis 序列化配置
- Redis 工具类
- 通用返回结果封装
- 方法一:Graphics2D 画图实现
- 1、验证码工具类
- 2、Controller 控制层
- 方法二:kaptcha 依赖实现
- 1、验证码配置
- 2、验证码文本生成器
- 3、测试正码生成
通用公共配置
公共依赖
SpringBoot项目
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 com.github.penggle kaptcha 2.3.2
配置 application.yml 文件
spring: # redis 配置 redis: # 地址 host: localhost # 端口,默认为6379 port: 6379 # 数据库索引 database: 0 # 密码 password: # 连接超时时间 timeout: 10s lettuce: pool: # 连接池中的最小空闲连接 min-idle: 0 # 连接池中的最大空闲连接 max-idle: 8 # 连接池的最大数据库连接数 max-active: 8 # #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms
公共常量
/** * 公共常量 */public class ConstantCode { /** * 系统缓存常量 */ public static final String KAPTCHA_KEY = "ss:kaptcha"; public static final Long REDIS_EXP_TIME = 60 * 60L; public static final Long KAPTCHA_EXP_TIME = 60 * 3L; /** * 验证码宽高常量 */ public static final Integer WIDTH = 200; public static final Integer HEIGHT = 55; /** * 图片后缀名称 */ public static final String IMG_JPG = "jpg"; public static final String IMG_PNG = "png"; public static final String IMG_JPEG = "jpeg";}Redis 序列化配置
import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;@Configurationpublic class RedisConfig { @Bean @SuppressWarnings("all") public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 为了自己开发方便,一般直接使用 RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); // Json序列化配置 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // String 的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; }}Redis 工具类
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.Collection;import java.util.concurrent.TimeUnit;/** * Redis工具类 */@Component@SuppressWarnings("all")public class RedisUtils { /** * 日志对象 */ private static Logger logger = LoggerFactory.getLogger(RedisUtils.class); @Autowired private RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public T getCacheObject(final String key) { ValueOperations operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection keys(final String pattern) { return redisTemplate.keys(pattern); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { logger.error("RedisUtils expire(String key,long time) failure." + e.getMessage()); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { logger.error("RedisUtils hasKey(String key) failure." + e.getMessage()); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } //============================String============================= /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { logger.error("RedisUtils set(String key,Object value) failure." + e.getMessage()); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { logger.error("RedisUtils set(String key,Object value,long time) failure." + e.getMessage()); return false; } }}通用返回结果封装
import org.springframework.http.HttpStatus;import org.springframework.util.StringUtils;import java.util.HashMap;/** * 操作消息提醒 */public class Result extends HashMap { private static final long serialVersionUID = 1L; /** * 状态码 */ public static final String CODE_TAG = "code"; /** * 返回内容 */ public static final String MSG_TAG = "msg"; /** * 数据对象 */ public static final String DATA_TAG = "data"; /** * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 */ public Result() { } /** * 初始化一个新创建的 AjaxResult 对象 * * @param code 状态码 * @param msg 返回内容 */ public Result(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一个新创建的 AjaxResult 对象 * * @param code 状态码 * @param msg 返回内容 * @param data 数据对象 */ public Result(int code, String msg, Object data) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (!StringUtils.isEmpty(data)) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static Result success() { return Result.success("操作成功"); } /** * 返回成功数据 * * @return 成功消息 */ public static Result success(Object data) { return Result.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static Result success(String msg) { return Result.success(msg, null); } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static Result success(String msg, Object data) { return new Result(HttpStatus.OK.value(), msg, data); } /** * 返回错误消息 * * @return */ public static Result error() { return Result.error("操作失败"); } /** * 返回错误消息 * * @param msg 返回内容 * @return 警告消息 */ public static Result error(String msg) { return Result.error(msg, null); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static Result error(String msg, Object data) { return new Result(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data); } /** * 返回错误消息 * * @param code 状态码 * @param msg 返回内容 * @return 警告消息 */ public static Result error(int code, String msg) { return new Result(code, msg, null); } /** * 方便链式调用 * * @param key 键 * @param value 值 * @return 数据对象 */ @Override public Result put(String key, Object value) { super.put(key, value); return this; }}方法一:Graphics2D 画图实现
使用 jdk 画图 Graphics2D 生成验证码
()1、验证码工具类
import java.awt.*;import java.awt.image.BufferedImage;import java.util.Random;/** * 验证码工具类 */public class VerifyCodeUtils { /** * 传入BufferedImage对象,并将生成好的验证码保存到BufferedImage中 */ public static String drawRandomText(BufferedImage bufferedImage, int width, int height) { Graphics2D graphics = (Graphics2D) bufferedImage.getGraphics(); // 验证码背景色 graphics.setColor(new Color(255, 255, 255)); // 填充线条背景 graphics.fillRect(0, 0, width, height); graphics.setFont(new Font("宋体,楷体,微软雅黑", Font.BOLD, 35)); // 数字和字母的组合 String baseNumLetter = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; StringBuilder builder = new StringBuilder(); // 旋转原点的 x 坐标 int x = 40; String ch; Random random = new Random(); for (int i = 0; i2、Controller 控制层
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.FastByteArrayOutputStream;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import javax.imageio.ImageIO;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletResponse;import java.awt.image.BufferedImage;import java.io.IOException;import java.util.Base64;import java.util.UUID;@RestControllerpublic class VerificationController { @Autowired private RedisUtils redisUtils; /** * 验证码生成工具,返回图片信息 * 适合学习练手使用,redis 呢能存在一个 * @param response * @throws IOException */ @GetMapping("/getImageCode") public void getImageCode(HttpServletResponse response) throws IOException { // 禁止缓存 response.setDateHeader("Expires", 0); response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); // 设置响应格式为png图片 response.setContentType("image/png"); // 生成图片验证码 BufferedImage image = new BufferedImage(ConstantCode.WIDTH, ConstantCode.HEIGHT, BufferedImage.TYPE_INT_RGB); String randomText = VerifyCodeUtils.drawRandomText(image, ConstantCode.WIDTH, ConstantCode.HEIGHT); // 存入redis redisUtils.set(ConstantCode.KAPTCHA_KEY, randomText, ConstantCode.KAPTCHA_EXP_TIME); ServletOutputStream out = response.getOutputStream(); ImageIO.write(image, ConstantCode.IMG_JPG, out); out.flush(); out.close(); } /** * 验证码 Base64 * 可生成多个 * @return */ @GetMapping("/getCaptchaInfo") public Result getCaptchaInfo() { Result success = Result.success(); // 生成图片验证码 BufferedImage image = new BufferedImage(ConstantCode.WIDTH, ConstantCode.HEIGHT, BufferedImage.TYPE_INT_RGB); String uuid = UUID.randomUUID().toString().replace("-", ""); // 验证码key String verifyKey = ConstantCode.KAPTCHA_KEY + uuid; String randomText = VerifyCodeUtils.drawRandomText(image, ConstantCode.WIDTH, ConstantCode.HEIGHT); // 存入redis redisUtils.set(verifyKey, randomText, ConstantCode.KAPTCHA_EXP_TIME); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return Result.error(e.getMessage()); } success.put("uuid", uuid); success.put("img", Base64.getEncoder().encodeToString(os.toByteArray())); return success; }}方法二:kaptcha 依赖实现
1、验证码配置
import com.google.code.kaptcha.impl.DefaultKaptcha;import com.google.code.kaptcha.util.Config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Properties;import static com.google.code.kaptcha.Constants.*;/** * 验证码配置 */@Configurationpublic class CaptchaConfig { @Bean(name = "captchaProducer") public DefaultKaptcha getKaptchaBean() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } @Bean(name = "captchaProducerMath") public DefaultKaptcha getKaptchaBeanMath() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 边框颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); // 验证码文本生成器 注意:KaptchaTextCreator的路径 properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.test.demo.KaptchaTextCreator"); // 验证码文本字符间距 默认为2 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 验证码噪点颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); // 干扰实现类 properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; }}2、验证码文本生成器
import com.google.code.kaptcha.text.impl.DefaultTextCreator;import java.util.Random;/** * 验证码文本生成器 */public class KaptchaTextCreator extends DefaultTextCreator { private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); @Override public String getText() { Integer result = 0; Random random = new Random(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); int randomoperands = (int) Math.round(Math.random() * 2); if (randomoperands == 0) { result = x * y; suChinese.append(CNUMBERS[x]); suChinese.append("*"); suChinese.append(CNUMBERS[y]); } else if (randomoperands == 1) { if (!(x == 0) && y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); suChinese.append("/"); suChinese.append(CNUMBERS[x]); } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append("+"); suChinese.append(CNUMBERS[y]); } } else if (randomoperands == 2) { if (x >= y) { result = x - y; suChinese.append(CNUMBERS[x]); suChinese.append("-"); suChinese.append(CNUMBERS[y]); } else { result = y - x; suChinese.append(CNUMBERS[y]); suChinese.append("-"); suChinese.append(CNUMBERS[x]); } } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append("+"); suChinese.append(CNUMBERS[y]); } suChinese.append("=?@" + result); return suChinese.toString(); }}3、测试正码生成
import com.google.code.kaptcha.Producer;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.FastByteArrayOutputStream;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.IOException;import java.util.Base64;import java.util.UUID;/** * 验证码操作处理 */@RestControllerpublic class CaptchaController { @Resource(name = "captchaProducer") private Producer captchaProducer; @Resource(name = "captchaProducerMath") private Producer captchaProducerMath; @Autowired private RedisUtils redisUtils; /** * 文本验证码 * * @return */ @GetMapping("/captchaCharImage") public Result getCharCode() { Result ajax = Result.success(); // 生成验证码 String capText = captchaProducerMath.createText(); String capStr = capText.substring(0, capText.lastIndexOf("@")); String code = capText.substring(capText.lastIndexOf("@") + 1); BufferedImage image = captchaProducerMath.createImage(capStr); // 保存验证码信息 String uuid = UUID.randomUUID().toString().replace("-", ""); String verifyKey = ConstantCode.KAPTCHA_KEY + uuid; redisUtils.set(verifyKey, code, ConstantCode.KAPTCHA_EXP_TIME); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return Result.error(e.getMessage()); } ajax.put("uuid", uuid); ajax.put("img", Base64.getEncoder().encodeToString(os.toByteArray())); return ajax; } /** * 生成验证码 */ @GetMapping("/captchaMathImage") public Result getMathCode() { Result ajax = Result.success(); // 生成验证码 String capText = captchaProducer.createText(); BufferedImage image = captchaProducerMath.createImage(capText); // 保存验证码信息 String uuid = UUID.randomUUID().toString().replace("-", ""); String verifyKey = ConstantCode.KAPTCHA_KEY + uuid; redisUtils.set(verifyKey, capText, ConstantCode.KAPTCHA_EXP_TIME); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return Result.error(e.getMessage()); } ajax.put("uuid", uuid); ajax.put("img", Base64.getEncoder().encodeToString(os.toByteArray())); return ajax; }}