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

13 篇文章 0 订阅
2 篇文章 1 订阅

1.背景

目前收到反馈,存在一类用户,在利用会员权益大量进行二次销售;而且还是自动进行操作的. 那么意味着他们有一个自动平台在对我们的商品进行二次销售.
这是就该我们的主角登场了. 验证码模块可以有效防止机器人刷接口

2.开源验证码框架

通过在网上查找资料, 发现了几个验证码开源框架

  1. Kaptcha: 是Google发布的一款Java验证码框架。该框架使用简单,支持生成图片和音频验证码,并且有多种配置选项供开发者进行定制。
  2. JCaptcha:是由SourceForge托管的一个开源Java验证码框架。该框架提供了基于DHTML的动态鱼眼效果来防止恶意程序自动猜解验证码,并且支持多种前端引擎,JCaptcha 即为 Java 版本的 CAPTCHA 项目,其是一个开源项目,支持生成图形和声音版的验证码,在生成声音版的验证码时,需要使用到 FreeTTS。
  3. SimpleCaptcha:是一个轻量级的Java验证码框架,使用简单方便,支持各种文字、图形、干扰线等特效的复杂验证码,Kaptcha也是基于SimpleCaptcha的验证码开源项目。
  4. Google Authenticator:是Google旗下的两步验证应用,同时也提供了基于Java的身份验证库,可用于保护Web应用中帐户的登录过程。咱不是很清楚,就不过多描述.
  5. tianai-captcha: 由国内开发者开发出的一套验证码框架. 支持 滑块验证, 旋转验证,文字点选验证等行为验证方式.开箱即,比较方便. 本文就基于这套开源代码展开说说

3.tianai-captcha

访问Gitee地址: tianai-captcha
以下部分文字是直接引用 tianai-captcha 中的内容.

3.1整体架构设计

tianai-captcha 验证码整体分为 生成器(ImageCaptchaGenerator)、校验器(ImageCaptchaValidator)、资源管理器(ImageCaptchaResourceManager) 其中生成器、校验器、资源管理器等都是基于接口模式实现 可插拔的,可以替换为自定义实现,灵活度高

  • 生成器(ImageCaptchaGenerator)
    • 主要负责生成行为验证码所需的图片
  • 校验器(ImageCaptchaValidator)
    • 主要负责校验用户滑动的行为轨迹是否合规
  • 资源管理器(ImageCaptchaResourceManager)
    • 主要负责读取验证码背景图片和模板图片等
    • 资源管理器细分为 资源存储(ResourceStore)、资源提供者(ResourceProvider)
      • 资源存储(ResourceStore) 负责存储背景图和模板图
      • 资源提供者(ResourceProvider) 负责将资源存储器中对应的资源转换为文件流
        • 一般资源存储器中存储的是图片的url地址或者id之类, 资源提供者 就是负责将url或者别的id转换为真正的图片文件
  • 图片转换器 (ImageTransform)
    • 主要负责将图片文件流转换成字符串类型,可以是base64格式/url 或其它加密格式,默认实现是bas64格式;

下面我们来依次解析

3.2 生成器 ImageCaptchaGenerator

ImageCaptchaGenerator 作为一个图片生成器接口,其中包含了 8 个方法

/**
     * 初始化
     *
     * @param initDefaultResource 是否初始化默认资源
     * @return ImageCaptchaGenerator
     */
    ImageCaptchaGenerator init(boolean initDefaultResource);

    /**
     * 生成验证码图片
     *
     * @param type 类型 {@link CaptchaTypeConstant}
     * @return SliderCaptchaInfo
     */
    ImageCaptchaInfo generateCaptchaImage(String type);


    /**
     * 生成滑块验证码
     *
     * @param type             type {@link CaptchaTypeConstant}
     * @param targetFormatName jpeg或者webp格式
     * @param matrixFormatName png或者webp格式
     * @return SliderCaptchaInfo
     */
    ImageCaptchaInfo generateCaptchaImage(String type, String targetFormatName, String matrixFormatName);

    /**
     * 生成滑块验证码
     *
     * @param param 生成参数
     * @return SliderCaptchaInfo
     */
    ImageCaptchaInfo generateCaptchaImage(GenerateParam param);


    /**
     * 获取滑块验证码资源管理器
     *
     * @return SliderCaptchaResourceManager
     */
    ImageCaptchaResourceManager getImageResourceManager();

    /**
     * 设置滑块验证码资源管理器
     *
     * @param imageCaptchaResourceManager
     */
    void setImageResourceManager(ImageCaptchaResourceManager imageCaptchaResourceManager);

    /**
     * 获取图片转换器
     *
     * @return ImageTransform
     */
    ImageTransform getImageTransform();

    /**
     * 设置图片转换器
     *
     * @param imageTransform imageTransform
     * @return ImageTransform
     */
    void setImageTransform(ImageTransform imageTransform);

从上面的接口挑了几个,分析下源码, 也让自己了解的更多.
一种比较好的学习方法就是把自己所学的东西 教授给别人,由此可以发现自己的不足以及盲区.

3.2.1生成器初始化方法init
 /**
     * 初始化
     * @param initDefaultResource 是否初始化默认资源
     * @return
     */
    @Override
    public ImageCaptchaGenerator init(boolean initDefaultResource) {
        // 这里进行初始化,只需要初始化一次
        if (init) {
            return this;
        }
        init = true;
        try {
            log.info("图片验证码[{}]初始化...", this.getClass().getSimpleName());
            // 设置默认图片转换器
            if (getImageTransform() == null) {
                setImageTransform(new Base64ImageTransform());
            }
			// 具体初始化,这是一个抽象方法,由子类实现具体逻辑,比如滑块/旋转/字符验证码等子类
			// 待会可以看一下滑块子类的具体实现 ①
            doInit(initDefaultResource);
        } catch (Exception e) {
            init = false;
            log.error("[{}]初始化失败,ex", this.getClass().getSimpleName(), e);
            throw e;
        }
        return this;
    }
3.2.1验证码生成方法generateCaptchaImage
	// 这是一个重载的方法, 可以使用默认参数,也可以进行自定义传参
	@Override
    public ImageCaptchaInfo generateCaptchaImage(String type) {
    	// 这里调用了重载方法,传默认值
        return generateCaptchaImage(type, defaultBgImageType, defaultSliderImageType);
    }
	
    @SneakyThrows
    @Override
    public ImageCaptchaInfo generateCaptchaImage(String type, String backgroundFormatName, String templateFormatName) {
       // 进行验证码的生成, 随着代码的深入查看, 里面是一个 抽象方法,由子类生成验证码 随后我们会看到②
        return generateCaptchaImage(GenerateParam.builder()
                .type(type)
                .backgroundFormatName(backgroundFormatName)
                .templateFormatName(templateFormatName)
                .obfuscate(false)
                .build());
    }

	/**
     * 生成验证码方法
     *
     * @param param param
     * @return ImageCaptchaInfo
     */
    protected abstract ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param);

4.接下来看下具体的验证码类的实现

子类有以下几个:
在这里插入图片描述
我们在这重点看一下滑块验证码的源代码

4.1 滑块验证码生成器StandardSliderImageCaptchaGenerator

滑块验证码也是一种经常使用的方式.需要鼠标拖动到指定位置,来判断是否通过.
比如:
滑块验证码

doInit(boolean initDefaultResource)方法
这个方法是初始化资源的地方.初始化一些 系统的图片模板,系统的资源文件等

 @Override
    protected void doInit(boolean initDefaultResource) {
        if (initDefaultResource) {
        	// 初始化默认资源
            initDefaultResource();
        }
    }

/**
     * 初始化默认资源
     */
    public void initDefaultResource() {
    	// 获取资源存储的实例
        ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
        // 添加一些系统的资源文件
        resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));

        // 添加一些系统的 模板文件
        ResourceMap template1 = new ResourceMap(DEFAULT_TAG, 4);
        template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));
        template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));
        template1.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/matrix.png")));
        resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template1);

        ResourceMap template2 = new ResourceMap(DEFAULT_TAG, 4);
        template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));
        template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));
        template2.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/matrix.png")));
        resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template2);
    }

doGenerateCaptchaImage生成验证码方法
这就是验证码的重点方法之一.此方法里面有详细生成验证码的过程

 @SneakyThrows
    @Override
    public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
        Boolean obfuscate = param.getObfuscate();
        ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
        Collection<InputStream> inputStreams = new LinkedList<>();
        try {
            // 获取背景图片资源
            Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
            InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
            inputStreams.add(resourceInputStream);
            BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream);
            // 拷贝一份图片
            BufferedImage targetBackground = CaptchaImageUtils.copyImage(cutBackground, cutBackground.getType());

            // 获取固定、活动、滑块矩阵等模板图片资源,并将其加入输入流队列中,待使用
            InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME);
            inputStreams.add(fixedTemplateInput);
            BufferedImage fixedTemplate = CaptchaImageUtils.wrapFile2BufferedImage(fixedTemplateInput);

            InputStream activeTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME);
            inputStreams.add(activeTemplateInput);
            BufferedImage activeTemplate = CaptchaImageUtils.wrapFile2BufferedImage(activeTemplateInput);


            InputStream matrixTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME);
            inputStreams.add(matrixTemplateInput);
            BufferedImage matrixTemplate = CaptchaImageUtils.wrapFile2BufferedImage(matrixTemplateInput);

//        BufferedImage cutTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, CUT_IMAGE_NAME));

            // 获取随机的 X 和 Y 轴位置,在选取范围内生成一个位置点作为起点
            int randomX = ThreadLocalRandom.current().nextInt(fixedTemplate.getWidth() + 5, targetBackground.getWidth() - fixedTemplate.getWidth() - 10);
            int randomY = ThreadLocalRandom.current().nextInt(targetBackground.getHeight() - fixedTemplate.getHeight());
            // 将固定模板绘制在目标 Image 上
            CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, randomX, randomY);
            if (obfuscate) {
                // 加入混淆滑块
                int obfuscateX = randomObfuscateX(randomX, fixedTemplate.getWidth(), targetBackground.getWidth());
                CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, obfuscateX, randomY);
            }
            // 将裁剪后的背景图与活动模板进行合并,获取滑动块
            BufferedImage cutImage = CaptchaImageUtils.cutImage(cutBackground, fixedTemplate, randomX, randomY);
            CaptchaImageUtils.overlayImage(cutImage, activeTemplate, 0, 0);
            // 将滑块拼接到矩阵模板上
            CaptchaImageUtils.overlayImage(matrixTemplate, cutImage, 0, randomY);
            return wrapSliderCaptchaInfo(randomX, randomY, targetBackground, matrixTemplate, param, templateImages, resourceImage);
        } finally {
            // 使用完后关闭流
            for (InputStream inputStream : inputStreams) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

/**
     * 包装成 SliderCaptchaInfo
     *
     * @param randomX         随机生成的 x轴
     * @param randomY         随机生成的 y轴
     * @param backgroundImage 背景图片
     * @param sliderImage     滑块图片
     * @param param           接口传入参数
     * @return SliderCaptchaInfo
     */
    @SneakyThrows
    public SliderImageCaptchaInfo wrapSliderCaptchaInfo(int randomX,
                                                        int randomY,
                                                        BufferedImage backgroundImage,
                                                        BufferedImage sliderImage,
                                                        GenerateParam param,
                                                        ResourceMap templateResource,
                                                        Resource resourceImage) {
        ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource);

        return SliderImageCaptchaInfo.of(randomX, randomY,
                transform.getBackgroundImageUrl(),
                transform.getTemplateImageUrl(),
                resourceImage.getTag(),
                templateResource.getTag(),
                backgroundImage.getWidth(), backgroundImage.getHeight(),
                sliderImage.getWidth(), sliderImage.getHeight()
        );
    }

这段代码主要是实现了验证码图片的生成及其相关处理流程,具体包括:

1.通过 requiredRandomGetResource 方法获取指定类型和 tag 的随机背景图片资源。
2.将获取到的背景图片流转换为 BufferedImage 对象,并通过拷贝一份作为目标 Image。
3.获取固定、活动和滑块矩阵等模板图片资源,并将其加入输入流队列中,待使用。
4.在选取范围内获取随机的 X 和 Y 轴位置,在起始点处绘制固定模板。
5.混淆滑块模块,如果需要则在随机位置再挑选一个滑块绘制上去。
6.将裁剪后的背景图与活动模板进行合并,获取滑动块,然后将滑块拼接到矩阵模板上
7.包装成 SliderCaptchaInfo

以上就是生成验证码的过程; 至于验证期待下一篇文章吧~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

漫长的~以后

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值