目录
- 1.1、EasyCaptcha(优选,支持种类多,样式多,使用简单)
- 1.1.1、作用
- 1.1.2、官方信息
- 1.1.3、使用案例
- 1.1.4、依赖
- 1.1.5、代码
- 1.1.6、效果
- 1.1.7、拓展
- 1.2、kaptcha
- 1.2.1、作用
- 1.2.2、官方信息
- 1.2.3、使用案例
- 1.2.4、依赖
- 1.2.5、代码
- 1.2.6、效果
- 1.3、AJ-Captcha(TODO)
- 1.3.1、作用
- 1.3.2、官方信息
- 1.3.3、依赖
- 1.3.4、代码
- 1.3.5、效果
- 1.4、tianai-captcha(TODO)
- 1.4.1、作用
- 1.4.2、官方信息
- 1.4.3、依赖
- 1.4.4、代码
- 1.4.5、效果
- 1.5、hutool验证码
- 1.5.1、作用
- 1.5.2、官方信息
- 1.5.3、使用案例
- 1.5.4、依赖
- 1.5.5、代码
- 1.5.6、效果
- 1.5.7、拓展
- 1.6、实战
- 1.6.1、使用场景
- 1.6.2、用途讲解
- 1.6.2.1、流程串讲
- 1.6.2.2、后端验证码校验—网关层校验
- 1.6.2.3、后端验证码校验—直接校验
1.1、EasyCaptcha(优选,支持种类多,样式多,使用简单)
1.1.1、作用
用作Java图形验证码,可用于Java Web、JavaSE等项目,支持情况如下:
- 支持 数字 / 字母 png、数字 / 字母 gif、中文png、中文gif、算术png类型
- 支持设置字段长度、算数数字长度;当然每一种类型都有默认值
- 支持设置字体;当然也有默认字体
- 支持为数字 / 字母 类验证码设置“验证码文本类型”,包含类型有:字母数字混合(默认值)、纯数字、纯字母、纯大写字母、纯小写字母、数字大写字母
效果演示:
1.1.2、官方信息
- gitee项目代码
1.1.3、使用案例
- renren-security
1.1.4、依赖
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
1.1.5、代码
import com.wf.captcha.*;
public class Test {
public static void main(String[] args) {
/*
* 说明:下面一共展示了5种样式的详细使用方法,它们分别是:
* 1、字母/数字 png类型
* 2、字母/数字 gif类型
* 3、中文 png类型
* 4、中文 gif类型
* 5、算术 png类型
*/
// 1、字母/数字 png类型(支持样式:字母数字混合(默认值)、纯数字、纯字母、纯大写字母、纯小写字母、数字大写字母)
SpecCaptcha captcha = new SpecCaptcha();
// 设置验证码显示宽度;宽度默认值:130
// captcha.setWidth(150);
// 设置验证码显示高度;高度默认值:48
// captcha.setHeight(40);
// 设置验证码随机字符长度;默认值:5;比如:asdfg就是5个字符
// captcha.setLen(4);
// 设置验证码文本类型;默认值:TYPE_DEFAULT(字母数字混合)
// captcha.setCharType(Captcha.TYPE_NUM_AND_UPPER);
// 设置验证码的字体;默认值:actionj字体、加粗Font.BOLD、字体大小32磅
// captcha.setFont(new Font("Verdana", Font.PLAIN, 32));
// 验证码结果
System.out.println(">>>>>>>>>>>>>>>>>>>> 字母/数字 png类型 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha.text());
System.out.println("验证码Base64编码:" + captcha.toBase64());
System.out.println(">>>>>>>>>>>>>>>>>>>> 字母/数字 png类型 end <<<<<<<<<<<<<<<<<<<<");
// 2、字母/数字 gif类型(支持样式:字母数字混合(默认值)、纯数字、纯字母、纯大写字母、纯小写字母、数字大写字母)
GifCaptcha captcha2 = new GifCaptcha();
// 设置验证码显示宽度;宽度默认值:130
// captcha2.setWidth(150);
// 设置验证码显示高度;高度默认值:48
// captcha2.setHeight(40);
// 设置验证码随机字符长度;默认值:5;比如:asdfg就是5个字符
// captcha2.setLen(4);
// 设置验证码文本类型;默认值:TYPE_DEFAULT(字母数字混合)
// captcha2.setCharType(Captcha.TYPE_NUM_AND_UPPER);
// 设置验证码的字体;默认值:actionj字体、Font.BOLD(加粗)、32磅
// captcha2.setFont(new Font("Verdana", Font.PLAIN, 32));
// 验证码结果
System.out.println("\n\n\n>>>>>>>>>>>>>>>>>>>> 字母/数字 gif类型 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha2.text());
System.out.println("验证码Base64编码:" + captcha2.toBase64());
System.out.println(">>>>>>>>>>>>>>>>>>>> 字母/数字 gif类型 end <<<<<<<<<<<<<<<<<<<<");
// 3、中文 png类型
ChineseCaptcha captcha3 = new ChineseCaptcha();
// 设置验证码显示宽度;宽度默认值:130
// captcha3.setWidth(150);
// 设置验证码显示高度;高度默认值:48
// captcha3.setHeight(40);
// 设置验证码随机字符长度;默认值:4,详情可看ChineseCaptchaAbstract的无参构造函数;比如:高山流水就是4个字符
// captcha3.setLen(4);
// 设置验证码的字体;默认值:楷体、 Font.PLAIN(不加粗)、 28磅
// captcha3.setFont(new Font("黑体", Font.PLAIN, 32));
// 验证码结果
System.out.println("\n\n\n>>>>>>>>>>>>>>>>>>>> 中文 png类型 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha3.text());
System.out.println("验证码Base64编码:" + captcha3.toBase64());
System.out.println(">>>>>>>>>>>>>>>>>>>> 中文 png类型 end <<<<<<<<<<<<<<<<<<<<");
// 4、中文 gif类型
ChineseGifCaptcha captcha4 = new ChineseGifCaptcha();
// 设置验证码显示宽度;宽度默认值:130
// captcha4.setWidth(150);
// 设置验证码显示高度;高度默认值:48
// captcha4.setHeight(40);
// 设置验证码随机字符长度;默认值:4,详情可看ChineseCaptchaAbstract的无参构造函数;比如:高山流水就是4个字符
// captcha4.setLen(4);
// 设置验证码的字体;默认值:楷体、Font.PLAIN(不加粗)、28磅,详情可看ChineseCaptchaAbstract的无参构造函数
// captcha4.setFont(new Font("黑体", Font.PLAIN, 32));
// 验证码结果
System.out.println("\n\n\n>>>>>>>>>>>>>>>>>>>> 中文 gif类型 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha4.text());
System.out.println("验证码Base64编码:" + captcha4.toBase64());
System.out.println(">>>>>>>>>>>>>>>>>>>> 中文 gif类型 end <<<<<<<<<<<<<<<<<<<<");
// 5、算术 png类型
ArithmeticCaptcha captcha5 = new ArithmeticCaptcha(150, 40);
// 设置验证码显示宽度;宽度默认值:130
// captcha5.setWidth(150);
// 设置验证码显示高度;高度默认值:48
// captcha5.setHeight(40);
// 设置验证码数字长度;默认值:2,详情可看ArithmeticCaptchaAbstract的无参构造函数;比如:“3 + 2 = ?”就是2个字符
// captcha5.setLen(3);
// 设置验证码的字体;默认值:楷体, Font.PLAIN(平常字体), 28
// captcha5.setFont(new Font("黑体", Font.PLAIN, 32));
// 验证码结果
System.out.println("\n\n\n>>>>>>>>>>>>>>>>>>>> 算术 png类型 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha5.text());
System.out.println("验证码内容:" + captcha5.getArithmeticString());
System.out.println("验证码Base64编码:" + captcha5.toBase64());
System.out.println(">>>>>>>>>>>>>>>>>>>> 算术 png类型 end <<<<<<<<<<<<<<<<<<<<");
}
}
1.1.6、效果
查看验证码图片效果方式:
下面返回了Base64编码结果
,如果大家想查看具体图片效果,可以根据下面html
代码新建一个以html
后缀结尾的文件,然后将img
标签中的src
属性替换成验证码Base64编码结果
即可,如下:
<!DOCTYPE HTML>
<html>
<head>
<title>测试验证码</title>
<meta charset="utf-8">
</head>
<body>
<img src="验证码Base64编码"/>
</body>
代码执行结果:
>>>>>>>>>>>>>>>>>>>> 字母/数字 png类型 start <<<<<<<<<<<<<<<<<<<<
验证码结果:utGBu
验证码Base64编码:data:image/png;base64,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
>>>>>>>>>>>>>>>>>>>> 字母/数字 png类型 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 字母/数字 gif类型 start <<<<<<<<<<<<<<<<<<<<
验证码结果:Z3N6N
验证码Base64编码:data:image/gif;base64,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
>>>>>>>>>>>>>>>>>>>> 字母/数字 gif类型 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 中文 png类型 start <<<<<<<<<<<<<<<<<<<<
验证码结果:志报背发
验证码Base64编码:data:image/png;base64,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
>>>>>>>>>>>>>>>>>>>> 中文 png类型 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 中文 gif类型 start <<<<<<<<<<<<<<<<<<<<
验证码结果:清却衣是
验证码Base64编码:data:image/gif;base64,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
>>>>>>>>>>>>>>>>>>>> 中文 gif类型 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 算术 png类型 start <<<<<<<<<<<<<<<<<<<<
验证码结果:5
验证码内容:9-4=?
验证码Base64编码:data:image/png;base64,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
>>>>>>>>>>>>>>>>>>>> 算术 png类型 end <<<<<<<<<<<<<<<<<<<<
1.1.7、拓展
背景: 上面解释了如何获取验证码结果
以及验证码Base64编码字符串
,其实我们还可以直接把验证码结果输出到响应流中,请看下面代码
代码:
import com.wf.captcha.SpecCaptcha;
@GetMapping("/previewCaptcha")
public void previewCaptcha(HttpServletResponse response) throws IOException {
try (
ServletOutputStream outputStream = response.getOutputStream();
) {
// 将验证码图片返回到前端
response.setContentType("image/png");
SpecCaptcha captcha = new SpecCaptcha(150, 40);
captcha.setLen(4);
// 将验证码图片返回
captcha.out(outputStream);
// 将验证码结果存储到redis中,具体过程省略
String result = captcha.text();
// 存储到redis中……
} catch (Exception e) {
e.printStackTrace();
}
}
结果:
1.2、kaptcha
1.2.1、作用
用作Java图形验证码,可用于Java Web、JavaSE等项目,支持情况如下:
- 支持数字、字母验证码,默认支持
- 支持自定义验证码内容,比如:算数验证码
相比上面EasyCaptcha
来说,这种方式虽然实现比较复杂,但是留给开发者的可修改空间较大,用户可以根据自己的需要去自定义验证码图片样式以及内容等
1.2.2、官方信息
大型项目技术栈第九讲 kaptcha的使用
1.2.3、使用案例
- ruoyi-cloud
1.2.4、依赖
<dependency>
<groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.3</version>
</dependency>
1.2.5、代码
以下示例代码来自于: ruoyi-cloud项目的ruoyi-gateway模块下的config
目录下的CaptchaConfig
、KaptchaTextCreator
类,以及service
目录的impl
目录下的ValidateCodeServiceImpl
类
验证码配置类CaptchaConfig:
作用:定义两种类型的验证码,分别是:默认文本验证码、自定义算术验证码
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.*;
/**
* 验证码配置类
*
* @author ruoyi
*/
@Configuration
public 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");
// 随机生成字符的范围,默认值:abcde2345678gfynmnpwx
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
// 验证码文本字符长度 默认为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");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.gateway.config.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;
}
}
算术验证码生成器类KaptchaTextCreator:
作用:为上面验证码配置类CaptchaConfig
中getKaptchaBeanMath()
提供文本实现类
import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
* 验证码文本生成器
*
* @author ruoyi
*/
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 (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();
}
}
验证码实现处理类ValidateCodeServiceImpl:
说明:
- 针对生成验证码createCaptcha方法来说:首先通过
CaptchaProperties
配置类来判断是否需要生成验证码,以及生成的验证码类型,然后将创建的验证码对应uuid + Base64编码字符串返回给前端,另外一方面把验证码对应uuid+验证码结果存入redis - 针对校验验证码checkCaptcha方法来说:根据用户提交的uuid和验证码结果作为依托,通过uuid去redis中查询真实验证码结果,然后和用户提交的验证码结果作对比,一致说明ok,否则说明用户输入验证码结果有问题,那就需要刷新验证码图片进行重新输入
下面代码中用到的一些内容没有粘贴过来,大家可以去看ruoyi-cloud项目,ValidateCodeServiceImpl类的具体位置是:ValidateCodeServiceImpl
import com.google.code.kaptcha.Producer;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.exception.CaptchaException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.sign.Base64;
import com.ruoyi.common.core.utils.uuid.IdUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* 验证码实现处理
*
* @author ruoyi
*/
@Service
public class ValidateCodeServiceImpl implements ValidateCodeService
{
// 数字/字母文本验证码
@Resource(name = "captchaProducer")
private Producer captchaProducer;
// 算术验证码
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisService redisService;
@Autowired
private CaptchaProperties captchaProperties;
/**
* 生成验证码
*/
@Override
public AjaxResult createCaptcha() throws IOException, CaptchaException
{
AjaxResult ajax = AjaxResult.success();
boolean captchaEnabled = captchaProperties.getEnabled();
ajax.put("captchaEnabled", captchaEnabled);
if (!captchaEnabled)
{
return ajax;
}
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
String captchaType = captchaProperties.getType();
// 生成验证码
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
/**
* 校验验证码
*/
@Override
public void checkCaptcha(String code, String uuid) throws CaptchaException
{
if (StringUtils.isEmpty(code))
{
throw new CaptchaException("验证码不能为空");
}
if (StringUtils.isEmpty(uuid))
{
throw new CaptchaException("验证码已失效");
}
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisService.getCacheObject(verifyKey);
redisService.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha))
{
throw new CaptchaException("验证码错误");
}
}
}
1.2.6、效果
算数验证码:
文本验证码:
1.3、AJ-Captcha(TODO)
1.3.1、作用
支持滑动拼图
和文字点选
这两种方式验证码类型
1.3.2、官方信息
- gitee代码
1.3.3、依赖
整理麻烦,以后在整理
1.3.4、代码
整理麻烦,以后在整理
1.3.5、效果
整理麻烦,以后在整理
1.4、tianai-captcha(TODO)
1.4.1、作用
支持多种验证码方式,包括:
- 滑块验证码
- 旋转验证码
- 滑动还原验证码
- 文字点选验证码
实现效果如下图:
1.4.2、官方信息
- gitee项目
1.4.3、依赖
整理麻烦,以后在整理
1.4.4、代码
整理麻烦,以后在整理
1.4.5、效果
整理麻烦,以后在整理
1.5、hutool验证码
1.5.1、作用
用作Java图形验证码,可用于Java Web、JavaSE等项目,支持情况如下:
- 支持验证码图片的线段干扰、圆圈干扰、扭曲干扰3种干扰方式
- 默认情况下生成数字 / 字母混合的验证码图片,支持自定义验证码内容
1.5.2、官方信息
- hutool图形验证码文档
1.5.3、使用案例
- Snowy-Cloud
1.5.4、依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
1.5.5、代码
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.captcha.generator.RandomGenerator;
public class Test {
public static void main(String[] args) {
// 线段干扰验证码
// 参数含义:图片宽度、图片高度、字符个数、干扰线条数
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(100, 38, 4, 4);
// 验证码结果
System.out.println(">>>>>>>>>>>>>>>>>>>> 线段干扰验证码 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha.getCode());
System.out.println("验证码Base64编码:" + captcha.getImageBase64Data());
System.out.println(">>>>>>>>>>>>>>>>>>>> 线段干扰验证码 end <<<<<<<<<<<<<<<<<<<<\n\n\n");
// 圆圈干扰验证码
// 参数含义:图片宽度、图片高度、字符个数、干扰圆圈条数
CircleCaptcha captcha2 = CaptchaUtil.createCircleCaptcha(100, 38, 4, 5);
// 验证码结果
System.out.println(">>>>>>>>>>>>>>>>>>>> 圆圈干扰验证码 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha2.getCode());
System.out.println("验证码Base64编码:" + captcha2.getImageBase64Data());
System.out.println(">>>>>>>>>>>>>>>>>>>> 圆圈干扰验证码 end <<<<<<<<<<<<<<<<<<<<\n\n\n");
// 扭曲干扰验证码
// 参数含义:图片宽度、图片高度、字符个数、干扰线宽度
ShearCaptcha captcha3 = CaptchaUtil.createShearCaptcha(100, 38, 4, 5);
// 验证码结果
System.out.println(">>>>>>>>>>>>>>>>>>>> 扭曲干扰验证码 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha3.getCode());
System.out.println("验证码Base64编码:" + captcha3.getImageBase64Data());
System.out.println(">>>>>>>>>>>>>>>>>>>> 扭曲干扰验证码 end <<<<<<<<<<<<<<<<<<<<\n\n\n");
// 说明:自定义验证码代码以线段干扰验证码LineCaptcha为例,讲解相关使用方法,其中LineCaptcha可以替换成另外两种验证码类型,这里不在演示
// 自定义纯数字验证码(hutool官方)
// 参数含义:图片宽度、图片高度、字符个数、干扰线条数
LineCaptcha captcha4 = CaptchaUtil.createLineCaptcha(100, 38, 4, 4);
// 自定义纯数字的验证码(随机4位数字,可重复)
captcha4.setGenerator(new RandomGenerator("0123456789", 4));
// 验证码结果
System.out.println(">>>>>>>>>>>>>>>>>>>> 自定义纯数字验证码 start <<<<<<<<<<<<<<<<<<<<");
System.out.println("验证码结果:" + captcha4.getCode());
System.out.println("验证码Base64编码:" + captcha4.getImageBase64Data());
System.out.println(">>>>>>>>>>>>>>>>>>>> 自定义纯数字验证码 end <<<<<<<<<<<<<<<<<<<<");
}
}
1.5.6、效果
下面返回了Base64编码结果
,如果大家想查看具体图片效果,可以根据下面html
代码新建一个以html
后缀结尾的文件,然后将img
标签中的src
属性替换成验证码Base64编码结果
即可,如下:
<!DOCTYPE HTML>
<html>
<head>
<title>测试验证码</title>
<meta charset="utf-8">
</head>
<body>
<img src="验证码Base64编码"/>
</body>
代码执行结果:
>>>>>>>>>>>>>>>>>>>> 线段干扰验证码 start <<<<<<<<<<<<<<<<<<<<
验证码结果:4ose
验证码Base64编码:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAmCAYAAAAycj4zAAAEqklEQVR42u2Y+2tbZRjH9x/4sz/4g4g/CCJjKqNMSv1FpF5Qp5vKFOZQEFbmZWW6m1M2Joi6eUFZV6FeJjoYqFPKxAubTdNLQtI0vaVpSiwzxx6Tk560SU7O+z7meQ5J0zQ55Vzamvg+5eWQ837JS55Pn/e5bIEGtwduPE+rWWxLs/yQEphGh9M0QOrBaTRATQmkka+1/wUQESHCBBABRJgAIoBU2Pz0FfCca6ElbJOBLCVnwPtZmwDyXwCia0vg/2ZXGYYAsslAJn9+nSDEPO8LIJsN5HroWwIQ+u4FAM4EkM0EspAIQX/XDhj6oh3yizK9cwtIX+4qvJZ6Ge6VtsOOxFZ6dqY6oD9/zfF35zSAc99zeOwIg1uf0uHmXTrctkeHZ08y+OoKB8bdc95CWoWpiWkY7PeDt2+YnuPhKZDn/3EXiJZNwdCXD5LzlbnB8nunQDSuwWHlFdieuL3uOqp0gl78s2NKBuD+gwxu2qnXXe2dDDJZ5zCi07PQ/8dQ3TUxFgHGmDtAwpf3k+P/9K0cyDkF8qZymJyOEXFhsQcUlqL3KZaEzzPd9B73T6aP2fr+o10GDIyOgbHlaNAKAJc9HFpeNKC88zVzBCM+O0dOHx4MQOK6BBoeUDTOOcxLMviHR2g/MjnjHEh8uIucPt776iqxEyCjWpCcfY+0DcJaqK4G91E3roUtn4FXEzpcXaq9Pz0HtI9g7Fp2KUvO9hVhaHmtpqZQ0CHgC5FOXcjYB5KMe8jhvguPQCGruArkdPoEOfoT9aypDvdRdyr9huUzbnnSADISdTFRVNlsLE6O/luSTXWYR1AXjcTsAclnEjDYcx94u1tBlUZrip0A2SPvJEdXRse+9tWRgpGBut3yw5bP2H3CuLIQzMGPGfzk5ZRX3LRQcIwcvVZ+wOsLdQH/qHUgnGkQvLSXnJ0Yu1RX7ARIq3QnOZrByh9SDQX3UYd6y9dijFNlVZ3I2zp0OPQpg94BDgXdGZABj880mVcv1FsGEr36Njl66lfza8IJECxv0dG1DKFUgkFdS+IOW+dgnnjpA0blbq0qC/PM+R/sX2lY3loBgnrLQCrHIlaWGxFSDQZLY7sRsiLSij7vC3E4c5HD3tOsnF9Kyy6UUoSsa2O4EUCekZ9YlUNq2YgWIN3T8qOu/lAEdC3I4fFjzFGlFSzmBASCTeFadvzHU+Xl2izL7SrrI/U9U92H6rukeyt9xPIZdz1v/PdLSfNOHjV4pdlqCItVk53qqRKOGagNA4KRUepDsN9Yqw8Jan7bjeHxbmaa+EuJ3o5l1MVyfkgllbo6LItREw5NWIqiDQNi1qnjEz+3SXeXxyd2LPbXci9y4CyDQGS5W8dxycXfOGx9ztjv6bWf2EtRggkbu3ZsFiuBxWbi5X1VddAYrjeQPM/BodQB01kWzrpQZ9dwRFKvwiotjCQnhj1GZDK6Zrkrzyfdm/auB5CS/Z77haa7GClY3uITQXjzfa4kcCx9se9o3W80iQho2z4dOs4YM64buh5y5RwllaZp75DXmPbiwqQ/W4yQXC7v7vi92c0tKOtS9gogAoiAIoA0hgkgAogwAUQAESaANIn9C71gOzsIHclKAAAAAElFTkSuQmCC
>>>>>>>>>>>>>>>>>>>> 线段干扰验证码 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 圆圈干扰验证码 start <<<<<<<<<<<<<<<<<<<<
验证码结果:s024
验证码Base64编码:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAmCAYAAAAycj4zAAAFV0lEQVR42u2ZbUxbZRTH+e4XY0w0MTGLMTFZzGLUGMV98BUXjR8W3YyZiTPMbPNlcRuSbQkwtsUhDGRYjZvgGKMZ2xgTGIOyUQZYMkaB8DYoWGW8t/QVWvp+jz2H3Eawt9B7b9dKnn/zpHCfk957z+8+5zzn3CRgSiglMRcwIEwMCAPCxIAwIEwMCAPCtN6BcAEOuq/0QsXeq1Cw+SfIfek0fVd+Uw0D1+/Jc47gp3axHdKtZ+A94yF425AGW4zp8JXlNJQ5VODkXDG9x77uKshKe5xGQgPxeXyg3HUZcp7/QXCUfVoBbodH9DksgXnYbc6H1w37BccHc5kw7L0fk3s0zurgxOEN/w8gzYWt5PTC136Guxe6YNG29KQ6zE7oKNNC/qsKmlcXtIpeGTyMneYcuOnSgofzLT0MnB9a3b2wy5xH81vnMsAUsMt6fx63A4pykkMwEhqI1+WDvJeLKETN6c1hbca1EwQEQxiGtmhV7+ogZyMUNxd+lSGgfZYislMsXJP1Hi+dTyUI9dUZiQ/knkpHzq45Uh/R7uqBGrIbva2P+hxHbMXkaK1HF9EOwxXafWQ6Jtv93Wn7lQAUK94HjgtIB+IK+KFosg/e6q2FRzXn4OE/SuCx9lLYOtAAJTNDEABO0gXfyG4kRw/WD0e0G7wxRHaNOeqoz4EJHB3NreFa0S7F+K0sMMbHOiE7/QnIPfoszNtn6ZgkIFafG5J7quChIGWhsbnnGjj8XtEXfWXf7+Toie7JiHaTvdNkd3F3ZcxWK4YtBIIApcrhMEFe9iZyvn6kJXRcEpD9f2rI6bg6NPaZ0GrwBJde1dxfsLGzguaPjWlFXzgmcnQ07rRW24nxiT9WanJ1EZCjtlLJv1X6y4fk+ObGU8uOSwKCoQkdPi+wAnSLVppHMGKFCR0dvRah3alXfoxNHRT8pJpzCUifVy/pt9SqXHK6suST/8xJAvKI5jdyePfCXMyeSr7OWKst7sZioWJHHcHYo9sJ1Y1viP6dkaFb5PCCEy+A02mRF8i7/XUEBMHsDcbBatPflFfkFDo43kAaXHcJxvaxPbDILd2fGCg26xTkZDwDxw89CRP3u8LaSALSG0xMuLNamcif016Gr0fboMY0FiysApKcgSEoniGrzd1HMD42HadqHkGIgeEPhvUzhSnk7M7284J2kre9mCc+192m7W64XRbmGcVUv2iHKN45G7ekrnEPUD8L6w6p1XlNZRo5ulL5haBNZktPCAj+vXJEVRjiDqvFNg0nx7th26AqlF/4IRYKNg/R0YZhY0S7qf4Zsrv0ZZUsMNSuHnjTcFAWGP9+8qMdK4GJrtQRkNo6CSl9tZJ2WqrvmsjRQzdH1lTR12WpJDnOH7xybI3wrRRrYEEWwHIAiQSFgDzdoSRnz3icgheClTzaYEgTI13TKDn6emZDRDucRzsEI1aYI7DVjjCybOdCDcYHId7RkXLIqiuELwwP6tsFDTHx84le1BPr9Yeai9MDs4Lh6vsXC8nG4xTXFdD7pqiTizCUzlsQD0nOIXqXPZQrUnXNoF0whqp1bJeUG0Zgw51ymj87PSi+Qs5vCXVzsf3utCzScWzD4/94HOexTS9GBr+VYODLqNUajLGW5F0WtkiEdlj8wJUkRS67G0q2X4j4ggpfYK22ExPSSbsy4oupcCNhgfBbX6w7NgXDEq4YBPRURzl8NqymHpccwtClvdhDr3D52gS/cVeFr3bFvAfhhd3bdQWEKf5iQBiQ+Ch54zbBwYDEAcQyVexY9p1IYJLWO4ywWgFkVXsGhK0QlkNYDomjBEIWA/KAtSNlgEYiA/kH3fGHbpF0m+YAAAAASUVORK5CYII=
>>>>>>>>>>>>>>>>>>>> 圆圈干扰验证码 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 扭曲干扰验证码 start <<<<<<<<<<<<<<<<<<<<
验证码结果:ry5n
验证码Base64编码:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAmCAYAAAAycj4zAAAEgUlEQVR42u2Y/WtTZxTH+w8IY4iw32TsF3+RMZgv4A8iilK6/TIQGSJORWXiZCDbfhiILyC+/GC3laB2iC8VdU5rHVRkqGOzaZu+2Np2aZqkeWlzb5M0r22S5uY+x5xzSTRtkqbJvfVpeg885DZ5uL33+3nO+T7nqQE9uIoaXQIdyKJFKiJD9woLDR0IJxFqncpCWQpwqh6IcH5yDhCewSwLD7Ht9hCYTPAMh3sgrmEJ9q4V4PgOb9n36P/ERqJPdcRzvteBlBG3zoYJSMuVaEXG3rtqpOg8XqC8VyAHPxdBThWfIyUZHFovwr5PBQj75bJN3b7Ho++yisXPX/lo5Xf9HS86758HMZpXfyxQkbGL9QGYaAiCpW5MB5Ivmg1R8NgVf2g4HoRkguWdd2aPn+YMGmcqMnXMkp4PLNny1bNyRAeSL3760gf7PxOg7a+YJob+rqnjGD/pz173rbaB/RthjtkvayAPG6Ik+qWjAdUNfbZp+2+G6TphS+q7rELhNCdJdMySqbCcY+aHN5Zv5qWGHGcQfBwlKM7vJnQgGD/WeQkKGriaZr6QMG91cZEpZQGxBk+r+hB3LkZI/HMH3nbTp76u3MwXEkxi8P8mJzi+FZcOkC6xFrrFL2A0dFHVh7APJOH7bV4qT1i21DLzTLz6yFrSvMADpXRFjTG+gcgsAfbQeTAJ22jEJAdMJ62qPsjtC0qWPP9jGprOKWb+6HJUlXtbd42T0Ni1F+3qg0pXjzsvLoFkANiCZ+lzMv4Mk1uTBxnuniEIJ3b6qYNX08wn70aUXVZTuKTdGG6HFxod8g0aiwIER5ewXfPVcWzLBEHRwsxR6KENTvKKwqVAmdfz4UjZQERmhmkIgI291KZkZYC4IgbNgVw/E84CUdvMh3e4SWxLrbvgnMRosqSDyPmgdMo36bNXvp++vgUmuUl9IKFEp+ZAhjqVsoUli6nceki+FJi3uN6adp77ey8H6XfzZldFQHD0yc05fw/JTyh75iv5JQNhTNIcCDaDCMTwQ1CjLhDA1xjK6c6xMQzcj5Dhv15jV45XTvsrAvJaboEUSOBj1vS/lHJ+G5Rb09+lKgeyGIFlCoHkO9dSM7BsYVl69+hkcJ0jW66S41LZQNrl6yCwQYiAACFwp93EASIMwRh0wwBrgTb5SnoYwMxaoZ/9SaOL3QATuwYv2W98AcETYASyWIHm3fexDRyHRYLhNoyD595YVkwvDJOYAvSDk7XDaNqoLexpQTH/ZfUVD26A4A4LvWMmXv62eia9v4lDeI6gbjCRoCPsGQk6wJo1EbMqgMSmGPg9KaURvOZd8oJyA6TQ6iyU7q/YXRKzdawRXsR/rRpB/2O/0Hvh++F74uLB98bFhDrg4mpjjWBkv5NOOGJpp0Ht0Oxr5kt1Y+QIGKNH84pZbauzg13NERPfd7aYqAvqk0/MUrv1YvNqqkXMYoJidqKgmK0oKO58ZoupRlQdkPctaFUBWWi6Y3lEMXkSlBsgxVYmeshyEJMbILqMfIHTgehA9NCBcApFB7IE4g2ZJfukmovrtAAAAABJRU5ErkJggg==
>>>>>>>>>>>>>>>>>>>> 扭曲干扰验证码 end <<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>> 自定义纯数字验证码 start <<<<<<<<<<<<<<<<<<<<
验证码结果:7231
验证码Base64编码:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAmCAYAAAAycj4zAAAEeElEQVR42u2Zf2hTVxTH/Wv/60CcwyH+YhMHmyLDgYqrMMSx/bExxpy/1tY6q65qpVCx1DI7NtbN1a7rVhV/zCkMZUhdbZnSakZV2qQ/kqatSdPGtr4kTfOz+f3eMefMhHY0mXl575Ho/cL9I3mHe1/OJ+fce86dBUwZpVnMBQwIEwPCgDAxIAzIc62axc0pDQYkS/TpnxsYkIwHMtsyL+UhVkJ0tDyyQ1WPCb74Rwd5d7RQoNLB8U4jXBu2gC/Cp/0jvREnXBn7AUp7N8PnmuWwXb0MirXvwHlzOXCBIdmce73qHFTm5CkPZIltuagXdgXDcExtgJ23exKOort6GHT7RDvF7NPDvu418FnHohkHwmkd/0NyGG2XGgmGJECeVrWTv8JL1oWgDmlERUYMRmn7ALRZHBDiBXoWFgRot7mgrOMBPf+yTQ/OKLxU5Qk7oLD7LXJ8lSEf+jz3o+v+G3EYGZdHvomDwWdSiI9GdFP1xTiMVIGI3tT/Ctyg6LjsF/fvusNNkLMRSpCfOS0hoMpo6kK7341jKa8Rc/jPpoMJbRotZ8imcmBL2jA4gxlO7To2DYYiQIyRQVhgXQL5rj2iF/pRO0SO1jk8Se0wXaFd8b2+lNc4qF1PzrYFRxLaRIQI2exQvyr6tzzqH4KrFXXTIFw48K1yQHImNsEy2wqw8xOiF8INHB0tPIUt2uWrtLJsvEHeR0ByNStEzzEVRPXHxdDVqJr2vaxATk7WUqr6zX9JkWMgpi0EggDlEJ6+EEj9UElaQE4XVICmoZX2kP+Ckg0Ix3PwsnURrLXnKHYuv2t1EpDaXrNkc+LGrnWr4HvDLoKxv/ttcIZsouezGB8mjRzZgOx1HaDouBVsUQQGprOjT05a/U6vJOkJ94qpx96v+j+h05gckhWIIWIkGOvtGxWLjismjmD80vdQkvm6XK1wRP8eRQaCwBoEoRzVvw+jfkN2ATnkLknrmJuqVJyDYGAtEpCgWp9J7rAd6kyHCMqertVppS1FgXgFL8yzvkInKx542WF0jLsIxuH7/VTNy63qwb0EBVspWQHkgu8iRUepp0x252jG3dTPwrrDqQAMFKar2OaeFUA+cHxEQNpDHbI65l70RJWrMIyY0i0OZQOyyvwTTB0rzSdgjmU+vMgtpM9yiBcEao3EWinukDQw8jpfJ0dPhLikdmEhRHZon/ER0hBopOjIde2WBQbuEcef9KtqeofjDUYpdMK4mxzdZDmb1K7d0Ux23xlyMx9ImaeCgNT56iWHYfb6qZOLMBrMVsnnVztvkqOLetaBn59MWJuU6N4lOwST8UC2OHcQkObg35K+rD0QIhh4GfV/DcZ0hG13dHZ534egc7fF2++YphAAXlrh85rB/dlRh7xme4OAjERGJX3Z+mixl+xiaqYhRhgZ2FpPdEGFo9ZURICyAshcywICInX9gd1bJYDEC037Vfh6YCtd4W5TL4WCzjcpKjBq5JKiF1RMyogBYUCYGBAGhIkBYUCYGBAG5NnRC4WraWS6HgOq7ECdHAdfZQAAAABJRU5ErkJggg==
>>>>>>>>>>>>>>>>>>>> 自定义纯数字验证码 end <<<<<<<<<<<<<<<<<<<<
1.5.7、拓展
背景: 上面解释了如何获取验证码结果
以及验证码Base64编码字符串
,其实我们还可以直接把验证码结果输出到响应流中,请看下面代码
代码:
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
@GetMapping("/previewCaptcha")
public void previewCaptcha(HttpServletResponse response) throws IOException {
try (
ServletOutputStream outputStream = response.getOutputStream();
) {
// 将验证码图片返回到前端
response.setContentType("image/png");
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(100, 38, 4, 4);
// 将验证码图片返回
captcha.write(outputStream);
// 将验证码结果存储到redis中,具体过程省略
String result = captcha.getCode();
// 存储到redis中……
} catch (Exception e) {
e.printStackTrace();
}
}
结果:
1.6、实战
1.6.1、使用场景
- 用户登录
- 注册账户
1.6.2、用途讲解
1.6.2.1、流程串讲
大家都看过验证码页面,比如ruoyi的登录页面:
所以需要用户进入该页面时就能把验证码给展示出来,这涉及到验证码图片获取接口,我已经在上面讲述了如何生成验证码图片的base64编码
考虑到用户需要提交用户输入验证码结果进行比对,我们在生成验证码的时候需要把验证码结果存储起来,这一般使用两种方式
- 存储到session中:由于在分布式微服务应用中很少使用共享session方式了,所以这种一般不再使用
- 存储到redis中:在存储的时候,我们可以把uuid和验证码结果进行绑定存储到redis中,当把验证码结果返回给用户的时候,同时把和验证码结果返回的uuid返回给用户
下面以把数据存储到redis中的方式进行讲解后续的步骤
以登录场景为例,用户输入用户名、密码、验证码之后点击登录按钮,由于我们之前已经把和验证码一一对应的uuid返回给用户,所以前端同事在提交数据的时候需要也需要把uuid提交到后端,进而后端依次完成验证码、登录信息的校验
这时候流程走到了后端代码中,我们本次讲解验证码的验证过程,可以分为这么几种情况
- 网关层校验:适合分布式微服务代码,在gateway网关层完成验证码的校验工作,比如:ruoyi-cloud
- 过滤器校验:都挺适合,可以把过滤器代码放在网关模块中完成校验工作
- 直接校验:都挺适合,但是不够优雅,比如:renren-security
1.6.2.2、后端验证码校验—网关层校验
本次以ruoyi-cloud为例讲解
网关层拦截获取验证码请求:
作用:在网关层完成验证码获取工作
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import com.ruoyi.gateway.handler.ValidateCodeHandler;
/**
* 路由配置信息
*
* @author ruoyi
*/
@Configuration
public class RouterFunctionConfiguration
{
@Autowired
private ValidateCodeHandler validateCodeHandler;
@SuppressWarnings("rawtypes")
@Bean
public RouterFunction routerFunction()
{
return RouterFunctions.route(
RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
validateCodeHandler);
}
}
处理获取验证码请求:
作用:获取验证码,一方面将验证码和对应uuid返回给前端,另外一方面把uuid和验证码进行绑定并存储在redis中
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.ruoyi.common.core.exception.CaptchaException;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.gateway.service.ValidateCodeService;
import reactor.core.publisher.Mono;
/**
* 验证码获取
*
* @author ruoyi
*/
@Component
public class ValidateCodeHandler implements HandlerFunction<ServerResponse>
{
@Autowired
private ValidateCodeService validateCodeService;
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest)
{
AjaxResult ajax;
try
{
ajax = validateCodeService.createCaptcha();
}
catch (CaptchaException | IOException e)
{
return Mono.error(e);
}
return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax));
}
}
生成验证码和校验验证码接口:
作用:为上面提供生成验证码服务、当用户提交登录、注册请求时,用于校验验证码是否正确
import java.io.IOException;
import com.ruoyi.common.core.exception.CaptchaException;
import com.ruoyi.common.core.web.domain.AjaxResult;
/**
* 验证码处理
*
* @author ruoyi
*/
public interface ValidateCodeService
{
/**
* 生成验证码
*/
public AjaxResult createCaptcha() throws IOException, CaptchaException;
/**
* 校验验证码
*/
public void checkCaptcha(String key, String value) throws CaptchaException;
}
生成验证码和校验验证码实现类:
import com.google.code.kaptcha.Producer;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.exception.CaptchaException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.sign.Base64;
import com.ruoyi.common.core.utils.uuid.IdUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* 验证码实现处理
*
* @author ruoyi
*/
@Service
public class ValidateCodeServiceImpl implements ValidateCodeService
{
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisService redisService;
@Autowired
private CaptchaProperties captchaProperties;
/**
* 生成验证码
*/
@Override
public AjaxResult createCaptcha() throws IOException, CaptchaException
{
AjaxResult ajax = AjaxResult.success();
boolean captchaEnabled = captchaProperties.getEnabled();
ajax.put("captchaEnabled", captchaEnabled);
if (!captchaEnabled)
{
return ajax;
}
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
String captchaType = captchaProperties.getType();
// 生成验证码
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
/**
* 校验验证码
*/
@Override
public void checkCaptcha(String code, String uuid) throws CaptchaException
{
if (StringUtils.isEmpty(code))
{
throw new CaptchaException("验证码不能为空");
}
if (StringUtils.isEmpty(uuid))
{
throw new CaptchaException("验证码已失效");
}
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisService.getCacheObject(verifyKey);
redisService.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha))
{
throw new CaptchaException("验证码错误");
}
}
}
拦截登录、注册请求,校验验证码是否正确过滤器:
作用:拦截登录、注册请求,统一校验验证码是否正确
/**
* 验证码过滤器
*
* @author ruoyi
*/
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
{
private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
@Autowired
private ValidateCodeService validateCodeService;
@Autowired
private CaptchaProperties captchaProperties;
private static final String CODE = "code";
private static final String UUID = "uuid";
@Override
public GatewayFilter apply(Object config)
{
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
}
try
{
String rspStr = resolveBodyFromRequest(request);
JSONObject obj = JSON.parseObject(rspStr);
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
}
catch (Exception e)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
}
1.6.2.3、后端验证码校验—直接校验
本次以renren-security为例讲解
获取验证码方法:
import io.renren.common.exception.ErrorCode;
import io.renren.common.validator.AssertUtils;
import io.renren.modules.security.service.CaptchaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录
*
* @author Mark sunlightcs@gmail.com
*/
@RestController
@Api(tags="登录管理")
public class LoginController {
@Autowired
private CaptchaService captchaService;
@GetMapping("captcha")
@ApiOperation(value = "验证码", produces="application/octet-stream")
@ApiImplicitParam(paramType = "query", dataType="string", name = "uuid", required = true)
public void captcha(HttpServletResponse response, String uuid)throws IOException {
//uuid不能为空
AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL);
//生成验证码
captchaService.create(response, uuid);
}
}
获取验证码和校验验证码接口:
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 验证码
*
* @author Mark sunlightcs@gmail.com
*/
public interface CaptchaService {
/**
* 图片验证码
*/
void create(HttpServletResponse response, String uuid) throws IOException;
/**
* 验证码效验
* @param uuid uuid
* @param code 验证码
* @return true:成功 false:失败
*/
boolean validate(String uuid, String code);
}
获取验证码和校验验证码实现类:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.renren.common.redis.RedisKeys;
import io.renren.common.redis.RedisUtils;
import io.renren.modules.security.service.CaptchaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* 验证码
*
* @author Mark sunlightcs@gmail.com
*/
@Service
public class CaptchaServiceImpl implements CaptchaService {
@Autowired
private RedisUtils redisUtils;
@Value("${renren.redis.open: false}")
private boolean open;
/**
* Local Cache 5分钟过期
*/
Cache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(5, TimeUnit.MINUTES).build();
@Override
public void create(HttpServletResponse response, String uuid) throws IOException {
response.setContentType("image/gif");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
//生成验证码
SpecCaptcha captcha = new SpecCaptcha(150, 40);
captcha.setLen(5);
captcha.setCharType(Captcha.TYPE_DEFAULT);
captcha.out(response.getOutputStream());
//保存到Redis缓存
setCache(uuid, captcha.text());
}
@Override
public boolean validate(String uuid, String code) {
//获取验证码
String captcha = getCache(uuid);
//效验成功
if(code.equalsIgnoreCase(captcha)){
return true;
}
return false;
}
private void setCache(String key, String value){
if(open){
key = RedisKeys.getCaptchaKey(key);
redisUtils.set(key, value, 300);
}else{
localCache.put(key, value);
}
}
private String getCache(String key){
if(open){
key = RedisKeys.getCaptchaKey(key);
String captcha = (String)redisUtils.get(key);
//删除验证码
if(captcha != null){
redisUtils.delete(key);
}
return captcha;
}
String captcha = localCache.getIfPresent(key);
//删除验证码
if(captcha != null){
localCache.invalidate(key);
}
return captcha;
}
}
在登陆接口中校验验证码是否正确:
import io.renren.common.exception.ErrorCode;
import io.renren.common.validator.AssertUtils;
import io.renren.modules.security.service.CaptchaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录
*
* @author Mark sunlightcs@gmail.com
*/
@RestController
@Api(tags="登录管理")
public class LoginController {
@Autowired
private CaptchaService captchaService;
@PostMapping("login")
@ApiOperation(value = "登录")
public Result login(HttpServletRequest request, @RequestBody LoginDTO login) {
//效验数据
ValidatorUtils.validateEntity(login);
//验证码是否正确
boolean flag = captchaService.validate(login.getUuid(), login.getCaptcha());
if(!flag){
return new Result().error(ErrorCode.CAPTCHA_ERROR);
}
//用户信息
SysUserDTO user = sysUserService.getByUsername(login.getUsername());
SysLogLoginEntity log = new SysLogLoginEntity();
log.setOperation(LoginOperationEnum.LOGIN.value());
log.setCreateDate(new Date());
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
log.setIp(IpUtils.getIpAddr(request));
//用户不存在
if(user == null){
log.setStatus(LoginStatusEnum.FAIL.value());
log.setCreatorName(login.getUsername());
sysLogLoginService.save(log);
throw new RenException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
}
//密码错误
if(!PasswordUtils.matches(login.getPassword(), user.getPassword())){
log.setStatus(LoginStatusEnum.FAIL.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
sysLogLoginService.save(log);
throw new RenException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
}
//账号停用
if(user.getStatus() == UserStatusEnum.DISABLE.value()){
log.setStatus(LoginStatusEnum.LOCK.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
sysLogLoginService.save(log);
throw new RenException(ErrorCode.ACCOUNT_DISABLE);
}
//登录成功
log.setStatus(LoginStatusEnum.SUCCESS.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
sysLogLoginService.save(log);
return sysUserTokenService.createToken(user.getId());
}
}