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

news2024/11/19 16:48:22

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

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

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

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

相关文章

C++:采用哈希表封装unordered_map和unordered_set

目录 一. 如何使用一张哈希表封装unordered_map和unordered_set 二. 哈希表迭代器的实现 2.1 迭代器成员变量及应当实现的功能 2.2 operator函数 2.3 operator*和operator->函数 2.4 operator!和operator函数 2.5 begin()和end() 2.6哈希表迭代器实现代码 三. unord…

渗透测试--6.2.mdk3攻击wifi

前言 本次依然使用Kali虚拟机系统&#xff0c;win11主机&#xff0c;网卡Ralink 802.11 配合mdk3进行wifi伪造、连接设备查看、解除认证攻击。本次实验只用于学习交流&#xff0c;攻击目标为自家的手机热点&#xff0c;请勿违法使用&#xff01; 目录 前言 1.Deauth攻击原…

Electron简介、安装、实践

本文中的所有代码均存放在https://github.com/MADMAX110/my-electron-app Electron是什么&#xff1f; Electron是一个开源的框架&#xff0c;可以使用JavaScript, HTML和CSS来构建跨平台的桌面应用程序。Electron的核心是由Chromium和Node.js组成&#xff0c;它们分别提供了渲…

【springboot 开发工具】接口文档我正在使用它生成,舒坦

前言 先来描述下背景&#xff1a;由于新公司业务属于自研产品开发&#xff0c;但是发现各产品业务线对于接口文档暂时还是通过集成Swagger来维护&#xff0c;准确来说是knife4j&#xff08;Swagger的增强解决方案&#xff09;。但是对于5年的后端开发老说&#xff0c;早就厌倦…

Java-线程安全的四个经典案例和线程池

单例模式 有些对象&#xff0c;在一个程序中应该只有唯一 一个实例&#xff08;光靠人保证不靠谱 借助语法来保证&#xff09; 就可以使用单例模式 在单例模式下 对象的实例化被限制了 只能创建一个 多了的也创建不了 单例模式分为两种&#xff1a;饿汉模式和懒汉模式 饿汉模式…

[Java基础]—SpringBoot

Springboot入门 Helloworld 依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version> </parent><dependencies><depend…

软件测试基础知识整理(四)- 软件开发模型、测试过程模型

目录 一、软件开发模型 1.1 瀑布模型 1.1.1 特点 1.1.2 优缺点 1.2 快速原型模型&#xff08;了解&#xff09; 1.2.1 特点 1.2.2 优缺点 1.3 螺旋模型&#xff08;了解&#xff09; 1.3.1 特点 1.3.2 优缺点 二、测试过程模型 2.1 V模型&#xff08;重点&#xff…

LeetCode_29. 两数相除

目录 题目描述 思路分析 我的题解 题目描述 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff…

8个免费的高质量UI图标大全网站

UI图标素材是设计师必不可少的设计元素。 高质量的UI图标会让设计师的设计效率事半功倍。 本文分享8个免费的高质量UI图标大全网站。 即时设计资源社区 即时设计资源广场中精选了多款专业免费的UI图标设计资源&#xff0c;无需下载即可一键保存源文件&#xff0c;同时还提供…

深入浅析Linux Perf 性能分析工具及火焰图

Perf Event 子系统 Perf 是内置于 Linux 内核源码树中的性能剖析&#xff08;profiling&#xff09;工具。它基于事件采样的原理&#xff0c;以性能事件为基础&#xff0c;支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析。可用于性能瓶颈的查找与热点代码的定位…

Maven PKIX path building failed 错误提示

最近公司的项目突然出现了下面的提示。 PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target -> [Help 2]问题和解决 出现上面的提示的问题是因为 SSL 签名的问题。 …

经典面试题:理解Cookie和Session之间的区别

文章目录 一、Cookie概念先知1、Cookie是什么&#xff1f;2、Cookie从哪里来&#xff1f;3、Cookie要存到哪里去&#xff1f;4、Cookie是存在哪里的&#xff1f;5、浏览器是如何通过Cookie来记录的&#xff1f;6、Cookie的过期时间有什么用&#xff1f; 二、见见Cookie三、会话…

软件设计师考试笔记,已通过

目录 系统可靠度 外部实体 内聚类型 编译过程 逆波兰式 前驱图 scrum框架模型 编译和解释 有限自动机 聚簇索引和非聚簇索引 二叉树的前序,中序,后序遍历 动态规划贪心算法 算法 01背包问题 系统可靠度 1. 串联部件可靠度 串联部件想要这条路走通&#xff0c;只有…

软件测试行业7年了,薪资从10k到了22k,感觉到头了?

蜕变之前 明天的希望&#xff0c;让我们忘了今天的痛苦。 怎样区别一个废柴和一个精英&#xff1f;看外貌&#xff0c;看气质&#xff0c;看谈吐&#xff0c;看消费… 有人忙着把人和人进行分类&#xff0c;有人忙着怎么从这一阶层过渡到上一阶层。当你很累的时候&#xff0c…

引入外部文件实现步骤

1.引入数据库相关依赖 2.创建外部属性文件&#xff0c;properties格式&#xff0c;定义数据信息&#xff1a;用户名 密码 地址等 3.创建spring配置文件&#xff0c;引入context命名空间&#xff0c;引入属性文件&#xff0c;使用表达式完成注入 <beans xmlns"http://w…

交友项目【集成环信Api】

目录 1&#xff1a;自动装配 2&#xff1a;查询用户环信账户 3&#xff1a;环信ID查询用户信息 1&#xff1a;自动装配 在项目中集成环信API&#xff0c;完成即时通信等 环信官方文档地址&#xff1a;Java Server SDK [IM 开发文档] 自动装配模块&#xff1a; pom文件相关…

2.数据结构期末复习之顺序表和链表

1.表是可以是线性结构 学号姓名19(数据项)jams(数据项)20(数据项)ming(数据项) 19 jams或 20 ming是数据元表单个的是数据项‘’线性结构可以表示为 19 jams->20 ming2.什么是逻辑结构?:具有相同类型的有限序列(元素排序的位置,排兵布阵操作的方法) a1 a2 a3 .... an (空…

jenkins流水线使用入门示例

之前采用Jenkins的自由风格构建的项目&#xff0c;每个步骤流程都要通过不同的方式设置&#xff0c;并且构建过程中整体流程是不可见的&#xff0c;无法确认每个流程花费的时间&#xff0c;并且问题不方便定位问题。 Jenkins的Pipeline可以让项目的发布整体流程可视化&#xf…

低代码开发大势所趋,这款无代码开发平台你值得拥有

文章目录 什么是低代码iVX和其它低代码的平台的区别没有创新的“拼凑”&#xff0c;没有好东西iVX在线编辑器体验 什么是低代码 低代码&#xff08;Low Code&#xff09;是一种可视化的应用开发方法&#xff0c;用较少的代码、以较快的速度来交付应用程序&#xff0c;将程序员…

ElasticSearch漫游 (1.安装ELK)

前期准备&#xff1a; 请搭建好linux环境 推荐使用centos7系统请关闭linux防火墙请安装好docker 安装ES 创建网络 我们需要部署kibana容器&#xff0c;因此需要让es和kibana互联&#xff0c;这里先创建一个网络。 docker network create es-net加载es镜像 运行docker命令 部…