一文搞定验证码(下部分)

文章目录

    • 1.背景
    • 2.验证
    • 3.valid接口具体实现类SimpleImageCaptchaValidator

1.背景

上一篇文章讲了验证码生成的逻辑. 验证码-上篇.
大概来说就是:

  1. 服务端保存一些默认的验证码图片. 然后需要生成时创建一个包含随机字符的验证码字符图片
  2. 根据随机字符和一些参数(如宽度、高度、图像类型等)生成验证码图片。图像生成通常使用 Java 的 Graphics 类或第三方库(如 Google 的 kaptcha 等)实现。
  3. 将图像数据编码为 Base64 或二进制流等格式,并返回给客户端。
  4. 客户端在需要验证用户输入时再次请求服务端,并将用户输入的验证码字符串传回。
  5. 服务端根据上一步存储的验证码字符串和用户输入进行比较,如果匹配成功则验证通过,否则不通过。

下面我们就讲一下验证的过程

2.验证

根据 tianai-captcha 中源码, 我们拉下来看ImageCaptchaValidator接口
一文搞定验证码(下部分)_第1张图片

/**
 * @Author: 天爱有情
 * @date 2022/2/17 10:54
 * @Description 图片验证码校验器
 */
public interface ImageCaptchaValidator {

    /**
     * 计算滑块要背景图的百分比,基本校验
     *
     * @param pos    移动的位置
     * @param maxPos 最大可移动的位置
     * @return float
     */
    float calcPercentage(Number pos, Number maxPos);

    /**
     * 校验滑块百分比
     *
     * @param newPercentage 用户滑动的百分比
     * @param oriPercentage 正确的滑块百分比
     * @return boolean
     */
    boolean checkPercentage(Float newPercentage, Float oriPercentage);

    /**
     * 校验滑块百分比
     *
     * @param newPercentage 用户滑动的百分比
     * @param oriPercentage 正确的滑块百分比
     * @param tolerant      容错值
     * @return boolean
     */
    boolean checkPercentage(Float newPercentage, Float oriPercentage, float tolerant);

    /**
     * 用于生成验证码校验时需要的回传参数
     *
     * @param imageCaptchaInfo 生成的验证码数据
     * @return Map
     */
    Map generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);

    /**
     * 校验用户滑动滑块是否正确
     *
     * @param imageCaptchaTrack      包含了滑动轨迹,展示的图片宽高,滑动时间等参数
     * @param sliderCaptchaValidData generateSliderCaptchaValidData(生成的数据
     * @return boolean
     */
    boolean valid(ImageCaptchaTrack imageCaptchaTrack, Map sliderCaptchaValidData);
}

上面最主要的接口是 boolean valid(ImageCaptchaTrack imageCaptchaTrack, Map sliderCaptchaValidData); 验证接口. 来判断校验用户滑动滑块是否正确

3.valid接口具体实现类SimpleImageCaptchaValidator

一文搞定验证码(下部分)_第2张图片

/**
* 验证
**/
@Override
    public boolean valid(ImageCaptchaTrack imageCaptchaTrack, Map sliderCaptchaValidData) {
        // 读容错值
        Float tolerant = getFloatParam(TOLERANT_KEY, sliderCaptchaValidData, defaultTolerant);
        // 读验证码类型
        String type = getStringParam(TYPE_KEY, sliderCaptchaValidData, CaptchaTypeConstant.SLIDER);
        // 验证前
        // 在验证前必须读取 容错值 和验证码类型
        if (!beforeValid(imageCaptchaTrack, sliderCaptchaValidData, tolerant, type)) {
            return false;
        }
        Integer bgImageWidth = imageCaptchaTrack.getBgImageWidth();
        if (bgImageWidth == null || bgImageWidth < 1) {
            // 没有背景图片宽度
            return false;
        }
        List trackList = imageCaptchaTrack.getTrackList();
        if (CollectionUtils.isEmpty(trackList)) {
            // 没有滑动轨迹
            return false;
        }
        // 验证
        boolean valid = doValid(imageCaptchaTrack, sliderCaptchaValidData, tolerant, type);
        if (valid) {
            // 验证后
            valid = afterValid(imageCaptchaTrack, sliderCaptchaValidData, tolerant, type);
        }
        return valid;
    }

验证实现类中, 具体校验逻辑在 doValid() 方法中,最后两参数是容错值和校验类型

public boolean doValid(ImageCaptchaTrack imageCaptchaTrack,
                           Map sliderCaptchaValidData,
                           Float tolerant,
                           String type) {
        if (CaptchaUtils.isSliderCaptcha(type)) {
            // 滑动类型验证码
            return doValidSliderCaptcha(imageCaptchaTrack, sliderCaptchaValidData, tolerant, type);
        } else if (CaptchaUtils.isClickCaptcha(type)) {
            // 点选类型验证码
            return doValidClickCaptcha(imageCaptchaTrack, sliderCaptchaValidData, tolerant, type);
        }
        // 不支持的类型
        log.warn("校验验证码警告, 不支持的验证码类型:{}, 请手动扩展 cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator.doValid 进行校验扩展", type);
        return false;
    }

我们看滑动类型验证码方法 doValidSliderCaptcha

/**
     * 校验滑动验证码
     *
     * @param imageCaptchaTrack      sliderCaptchaTrack
     * @param sliderCaptchaValidData sliderCaptchaValidData
     * @param tolerant               tolerant
     * @param type                   type
     * @return boolean
     */
    public boolean doValidSliderCaptcha(ImageCaptchaTrack imageCaptchaTrack,
                                        Map<String, Object> sliderCaptchaValidData,
                                        Float tolerant,
                                        String type) {
        // 读取百分比
        Float oriPercentage = getFloatParam(PERCENTAGE_KEY, sliderCaptchaValidData);
        if (oriPercentage == null) {
            // 没读取到百分比
            return false;
        }
        List<ImageCaptchaTrack.Track> trackList = imageCaptchaTrack.getTrackList();
        // 取最后一个滑动轨迹
        ImageCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1);
        // 计算百分比
        float calcPercentage = calcPercentage(lastTrack.getX(), imageCaptchaTrack.getBgImageWidth());
        // 校验百分比
        return checkPercentage(calcPercentage, oriPercentage, tolerant);
    }

// 校验百分比是否符合
@Override
    public boolean checkPercentage(Float newPercentage, Float oriPercentage, float tolerant) {
        if (newPercentage == null || Float.isNaN(newPercentage) || Float.isInfinite(newPercentage)
                || oriPercentage == null || Float.isNaN(oriPercentage) || Float.isInfinite(oriPercentage)) {
            return false;
        }
        // 容错值
        float maxTolerant = oriPercentage + tolerant;
        float minTolerant = oriPercentage - tolerant;
        return newPercentage >= minTolerant && newPercentage <= maxTolerant;
    }

这里是基础的滑动模块校验. 校验了滑动的位置是否符合校验值.比较简单
下面我们来看下 点选模块的校验,就是在doValid方法中 doValidClickCaptcha

/**
     * 校验点选验证码
     *
     * @param imageCaptchaTrack      sliderCaptchaTrack
     * @param sliderCaptchaValidData sliderCaptchaValidData
     * @param tolerant               tolerant
     * @param type                   type
     * @return boolean
     */
    public boolean doValidClickCaptcha(ImageCaptchaTrack imageCaptchaTrack,
                                       Map sliderCaptchaValidData,
                                       Float tolerant,
                                       String type) {
        String validStr = getStringParam(PERCENTAGE_KEY, sliderCaptchaValidData, null);
        if (ObjectUtils.isEmpty(validStr)) {
            return false;
        }
        String[] splitArr = validStr.split(";");
        List trackList = imageCaptchaTrack.getTrackList();
        if (trackList.size() < splitArr.length) {
            return false;
        }
        // 取出点击事件的轨迹数据
        List clickTrackList = trackList
                .stream()
                .filter(t -> TrackTypeConstant.CLICK.equalsIgnoreCase(t.getType()))
                .collect(Collectors.toList());
        if (clickTrackList.size() != splitArr.length) {
            return false;
        }
        for (int i = 0; i < splitArr.length; i++) {
            ImageCaptchaTrack.Track track = clickTrackList.get(i);
            String posStr = splitArr[i];
            String[] posArr = posStr.split(",");
            float xPercentage = Float.parseFloat(posArr[0]);
            float yPercentage = Float.parseFloat(posArr[1]);

            float calcXPercentage = calcPercentage(track.getX(), imageCaptchaTrack.getBgImageWidth());
            float calcYPercentage = calcPercentage(track.getY(), imageCaptchaTrack.getBgImageHeight());
			// 判断每个点选值是否符合范围
            if (!checkPercentage(calcXPercentage, xPercentage, tolerant)
                    || !checkPercentage(calcYPercentage, yPercentage, tolerant)) {
                return false;
            }
        }
        return true;
    }

这段代码实现了校验点选验证码的功能,用于检查用户完成滑块验证时是否存在异常。具体而言,该方法接收一个滑块验证码的图片轨迹数据和验证信息等参数,通过计算出用户实际点击位置与给定位置之间的误差,并根据设置的容错范围判断该验证码是否通过验证。

具体流程如下:

  1. 首先从验证信息中获取验证位置的百分比,并检测是否为空。
  2. 根据返回结果分别获取用户完成验证时的轨迹列表和当前验证位置的数量。
  3. 检查验证位置的数量是否与点击事件的位置数一致。
  4. 对于每个点击事件,将其实际位置的百分比计算出来,并比较它与预期位置的误差值是否在可接受的容错范围内。
  5. 若全部验证通过,则返回true,否则返回false。

需要注意的是,这段代码仅提供了部分验证功能,并不能保证完全有效。因此,在实现安全性要求更高的验证码时,还需要考虑其他安全机制以防止恶意攻击
一文搞定验证码(下部分)_第3张图片

当然你也可以自己实现ImageCaptchaValidator类, 做自己定制化校验规则.
至此验证码生成,验证逻辑就梳理完了.
由于本人才疏学浅, 文章难免有错误的地方, 欢迎指正~

你可能感兴趣的:(验证码,springboot,工具类,java,开发语言,springboot)