前言
- 框架:Spring Boot 2.3.12.RELEASE
- 环境:JDK 1.8 + MySQL 8.0.19 + Redis 6.2.6
一、依赖导入
使用第三方依赖 kaptcha
<dependency>
<groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
<version>2.3.3</version>
</dependency>
值得注意的是,kaptcha 中内置了 Servlet 依赖
而 Spring Boot 内置了 Tomcat,避免发生依赖冲突,将 kaptcha 中的 Servlet 依赖使用 < exclusion >
标签排除
二、验证码文本生成器
顾名思义,用于生成验证码
的一个工具类
kaptcha 提供了一个默认的验证码文本生成器 DefaultTextCreator
,通过 getText() 方法来生成验证码
自定义
自定义验证码生成器,需要继承默认的验证码生成器 DefaultTextCreator
以数字运算验证码为例
- 定义一个字符串数组,元素是进行运算的数字
- 重写 getText() 方法
大致思路
- 定义变量:
运算结果
result、运算拼接
suC、两个随机数变量
x、y∈[0, 10] - 因为运算有四种,再定义一个随机数 [0,3] 变量 randomoperands 来判断进行哪种运算
- 将随机数作为数组下标,获取运算的两个数字
- 进行运算,将运算结果赋给 result
- 利用运算拼接变量 suC 凭借当前运算式子,凭借成
运算数字
运算符
运算数字
=?@
运算结果
- 验证码生成
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 = random.nextInt(3);
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 (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]);
}
}
suChinese.append("=?@" + result);
return suChinese.toString();
}
}
三、配置
对于验证码,有随机字符串形式的验证码,也有数学运算的验证码等多种实现方式
验证码的样式需要使用 kaptcha 提供的 DefaultKaptcha
进行相关配置,可以通过配置类的方式,使用 @Bean 注解将其注入到容器中去
@SpringBootConfiguration
public class CaptchaConfig {
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty(KAPTCHA_BORDER, "yes");
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.zh.system.util.KaptchaTextCreator");
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
样式是通过 DefaultKaptcha
的 setConfig()
方法进配置的,其所需要的是 kaptcha 提供的 Config 对象作为参数。
而 Config 对象需要一个 Properties 对象来进行验证码样式的设置,Properties 可以看成是 key-value 都是 String 类型的 Map 集合
也就是说,决定验证码样式的是一组组 String 类型的 key-value,其中的 key 是由 kaptcha 提供的接口Constants 中的常量
,value的设置也需要与常量向对应
四、实现
将验证码响应给前端
- 通过
captchaProducerMath
将验证码以自定义的样式生成,此时返回的是字符串类型
- 由上面可以,通过验证码生成器生成的验证码格式是:
数字 运算符 数字 = ?@ 结果
,所以对即进行拆分(split(“@”)),最终变成一个字符串数组,元素分别是数字 运算符 数字 = ?
和结果
- 将字符串数组第一个元素作为参数传入
captchaProducerMath.createImage()
方法,生成验证码图片 将结果保存在 redis 中
,验证时与 redis 中的值比较- 通过
FastByteArrayOutputStream
以及 JDK 提供的ImageIO
类的 write() 将图片变为字节数组 - 再将字节数组进行 Base64 编码的处理,同时需要注意,如果生成的编码超过 76 个字符时,会出现回车换行符
(\r\n)
,需要利用 replaceAll() 进行替换处理 - 将 Base64 编码响应给前端,前端 img 可以直接使用
代码
@Autowired
@Qualifier("captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisCache redisCache;
@GetMapping("/captchaImage")
public AjaxResult captchaImage(){
AjaxResult ajaxResult = AjaxResult.success();
String text = captchaProducerMath.createText();
String[] split = text.split("@");
String imgStr = split[0];
String code = split[1];
// 根据验证码,生成图片
BufferedImage image = captchaProducerMath.createImage(imgStr);
// 将验证码保存到 redis 缓存中
redisCache.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY + uuid, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 将图片转为字节数组,
FastByteArrayOutputStream stream = new FastByteArrayOutputStream();
try {
ImageIO.write(image, "jpeg", stream);
} catch (IOException e) {
e.printStackTrace();
}
// 通过 Base64 将字节数组进行编码
String imgBase64 = "data:image/jpeg;base64," + Base64.encode(stream.toByteArray());
// 使用 Bash64 时,如果超过 76 个字符时,会出现回车换行符:\r\n,所以需要去掉
imgBase64 = imgBase64.replaceAll("[\r\n]", "");
ajaxResult.put("img", imgBase64);
return ajaxResult;
}