Java编码技巧:验证码

news2024/12/23 5:31:02

目录

    • 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目录下的CaptchaConfigKaptchaTextCreator类,以及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:

作用:为上面验证码配置类CaptchaConfiggetKaptchaBeanMath()提供文本实现类

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1051423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BUUCTF reverse wp 21 - 30

[ACTF新生赛2020]rome 无壳, 直接拖进IDA32 y键把v2改成char[49], n键重命名为iuput int func() {int result; // eaxint v1[4]; // [esp14h] [ebp-44h]char input[49]; // [esp24h] [ebp-34h] BYREFstrcpy(&input[23], "Qsw3sj_lz4_Ujwl");printf("Please…

【知识点】JavaScript中require的一些理解

以下内容源自个人理解&#xff0c;若有错误欢迎指出。 猜想 多个文件中require同一个文件时&#xff0c;对于首次出现的require&#xff0c;会去读取文件并执行一遍&#xff0c;然后加入缓存&#xff1b;之后当再次require到这个文件时&#xff0c;只会指向这个缓存&#xff0c…

使用 Velocity 模板引擎的 Spring Boot 应用

使用 Velocity 模板引擎的 Spring Boot 应用 模板引擎是构建动态内容的重要工具&#xff0c;特别适用于生成HTML、邮件内容、报告和其他文本文档。Velocity是一个强大的模板引擎&#xff0c;它具有简单易用的语法和灵活性。本文将介绍如何在Spring Boot应用中使用Velocity模板…

2023-9-29 JZ27 二叉树的镜像

题目链接&#xff1a;二叉树的镜像 import java.util.*;/** public class TreeNode {* int val 0;* TreeNode left null;* TreeNode right null;* public TreeNode(int val) {* this.val val;* }* }*/public class Solution {/*** 代码中的类名、方法名、参数…

【算法基础】一文掌握十大排序算法,冒泡排序、插入排序、选择排序、归并排序、计数排序、基数排序、希尔排序和堆排序

目录 1 冒泡排序&#xff08;Bubble Sort&#xff09; 2 插入排序&#xff08;Insertion Sort&#xff09; 3 选择排序&#xff08;Selection Sort&#xff09; 4. 快速排序&#xff08;Quick Sort&#xff09; 5. 归并排序&#xff08;Merge Sort&#xff09; 6 堆排序 …

力扣 -- 44. 通配符匹配

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool isMatch(string s, string p) {int ms.size();int np.size();//为了调整映射关系s s;p p;//多开一行多开一列vector<vector<bool>> dp(m1,vector<bool>(n1,false));//初始化//dp[0]…

uniapp 实现下拉筛选框 二次开发定制

前言 最近又收到了一个需求&#xff0c;需要在uniapp 小程序上做一个下拉筛选框&#xff0c;然后找了一下插件市场&#xff0c;确实有找到&#xff0c;但不过他不支持搜索&#xff0c;于是乎&#xff0c;我就自动动手&#xff0c;进行了二开定制&#xff0c;站在巨人的肩膀上&…

asp.net core mvc 文件上传,下载,预览

//文件上传用到了IformFile接口 1.1文件上传视图 <form action"/stu/upload" method"post" enctype"multipart/form-data"><input type"file" name"img" /><input type"submit" value"上传&…

分类预测 | Matlab实现BES-ELM秃鹰搜索算法优化极限学习机分类预测

分类预测 | Matlab实现BES-ELM秃鹰搜索算法优化极限学习机分类预测 目录 分类预测 | Matlab实现BES-ELM秃鹰搜索算法优化极限学习机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 Matlab实现BES-ELM秃鹰搜索算法优化极限学习机分类预测&#xff08;完整源码和数…

深度学习算法在工业视觉落地的思考

0.废话 距离上次的栈板识别的思考已经过去3个月&#xff0c;中间根据客户的需求和自己的思考&#xff0c;对软件又重新做了调整。但是整体上还是不满意。 0.1 老生常谈的工业视觉落地架构 对于软件架构&#xff0c;我实在没有太多的参考。没办法&#xff0c;公司根本不关心软…

react+IntersectionObserver实现页面丝滑帧动画

实现效果&#xff1a; 加入帧动画前&#xff1a; 普通的静态页面 加入帧动画后&#xff1a; 可以看到&#xff0c;加入帧动画后&#xff0c;页面效果还是比较丝滑的。 技术实现 加入animation动画类 先用 **scss **定义三种动画类&#xff1a; .withAnimation {.fade1 {ani…

学会这些,QtIFW制作安装包不再是难题

一文看懂如何利用QtIFW制作安装包&#xff0c;小白也能看懂且学会的软件安装包制作教程&#xff1b;&#xff08;本文不基于Qt工程&#xff09; 1 前言 1.1 安装包制作工具的选择 安装程序生成工具就是将应用程序和依赖的文件打包到一个可执行的安装程序种&#xff0c;可以简…

HTML之如何下载网页中的音频(二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【算法】莫队

这篇博客起源于本人把一道 p o w ( 2 , n ) pow(2,n) pow(2,n) 的问题考虑成求组合数前缀和的问题qwq&#xff0c;于是接触到了这个新算法来总结一下 参考自这篇文章&#xff0c;写得太好了 首先是一道模板题 题目意思是&#xff0c;给出一个数组a&#xff0c;再给出多个区…

What is an HTTP Flood DDoS attack?

HTTP 洪水攻击是一种针对 Web 和应用程序服务器的第 7 层分布式拒绝服务 &#xff08;DDoS&#xff09; 攻击。HTTP 洪水攻击通过使用 HTTP GET 或 HTTP POST 请求执行 DDoS 攻击。这些请求是有效的&#xff0c;并且针对可用资源&#xff0c;因此很难防范 HTTP 洪水攻击。 匿名…

特斯拉——使用人工智能制造智能汽车

特斯拉(Tesla)是电动汽车开发和推广的先驱。特斯拉对自动驾驶汽车的未来寄予厚望--实际上&#xff0c;每一辆特斯拉汽车都有可能通过软件升级成为自动驾驶汽车。该公司还生产和销售高级电池和太阳能电池板。 汽车的自动驾驶是按从1~5的等级划分的。自适应巡航控制和自动停车系…

论文笔记(整理):轨迹相似度顶会论文中使用的数据集

0 汇总 数据类型数据名称数据处理出租车数据波尔图 原始数据&#xff1a;2013年7月到2014年6月&#xff0c;170万条数据 ICDE 2023 Contrastive Trajectory Similarity Learning with Dual-Feature Attention 过滤位于城市&#xff08;或国家&#xff09;区域之外的轨迹 过…

【SSL】用Certbot生成免费HTTPS证书

1. 实验背景 服务器&#xff1a;CentOS7.x 示例域名&#xff1a; www.example.com 域名对应的web站点目录&#xff1a; /usr/local/openresty/nginx/html 2. 安装docker # yum -y install yum-utils# yum-config-manager --add-repo https://download.docker.com/linux/ce…

采集SEO方法-优化内链与外链建设

采集大量的文章数据&#xff0c;要想批量做SEO优化添加内链外链方法&#xff0c;可以使用简数采集器的处理规则实现。 简数采集器的一个处理规则&#xff0c;可以包含多种SEO方法&#xff0c;还可自由组合&#xff0c;强大灵活方便。 优化内链外链的SEO技巧&#xff1a; 1&a…

基于PYQT5的GUI开发系列教程【二】QT五个布局的介绍与运用

目录 本文概述 作者介绍 创建主窗口 水平布局 垂直布局 栅格布局 分裂器水平布局 分裂器垂直布局 自由布局 取消原先控件的布局的方法 尾言 本文概述 PYQT5是一个基于python的可视化GUI开发框架&#xff0c;具有容易上手&#xff0c;界面美观&#xff0c;多平台…