图形验证码从设计到实现

news2024/11/13 10:24:22

在这里插入图片描述

验证码的使用场景

图形验证码在我们的日常使用中是非常常见的,比如一些App、小程序、PC网站等。涉及到的业务也比较广泛、例如用户登录流程、注册流程、找回密码。下面我们来大致梳理下上述流程:

  1. 登录流程
  • 用户首先在登录界面输入手机号
  • 然后通过图形验证码验证,只有成功通过验证码后才能继续下一步
  • 成功通过图形验证码后,系统向用户手机发送一条包含数字验证码的短信
  • 用户收入收到的验证码,系统验证无误后允许登录
  1. 注册流程
  • 用户填写基本信息,包括手机号
  • 提交信息前需要通过图形验证码验证
  • 通过图形验证码后,系统发送短信验证码到用户手机
  • 用户输入短信验证码完成注册过程
  1. 找回密码
  • 用户输入与账号关联的手机号
  • 进行图形验证码验证
  • 通过验证后,系统发送密码重置链接或重置密码所需的验证码至用户的手机
  • 用户按照指引重置密码

验证码的生成步骤

知道了验证码的使用区域,下面来简单说下验证码的生成

  1. 背景生成
    • 选择一个随机颜色作为背景色。
    • 可以添加一些随机的干扰线条或点来增加破解难度。
  2. 字符生成
    • 从预设的字符集中随机选取几个字符组成验证码字符串。这些字符可以是数字、小写字母或大写字母
    • 对于每个字符,可以随机选择不同的字体、大小以及倾斜度。
  3. 字符渲染
    • 将生成的字符绘制在背景上。为了进一步提高安全性,可以对字符应用扭曲效果或者随机旋转一定角度
    • 字符之间的间距也应该是随机的,以便于增加识别难度
  4. 噪声添加
    • 在背景中随机位置添加噪声点或线,这有助于迷惑OCR(光学字符识别)工具。
    • 可以使用不同的颜色和形状,使得噪声更加自然。
  5. 图形变形
    • 整个图像可以应用轻微的扭曲或变形,以使得验证码更难以被自动化工具识别。
  6. 输出图像
    • 最后将处理好的图像输出为JPEG或PNG格式,并且可能还需要设置适当的分辨率和压缩级别以保证质量
  7. 存储验证码值及过期机制
    • 验证码的实际文本需要存储在服务器端,并且通常会关联一个会话ID或者令牌,这样当用户提交表单时,可以验证输入是否正确
    • 设置验证码的有效时间,超出这个时间则认为验证码无效。

验证码的代码实现

创建springBoot项目,导入kaptcha相关依赖

 <dependency>
    <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
  <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>kaptcha-spring-boot-starter</artifactId>
      <version>1.1.0</version>
  </dependency>

配置验证码的生成方法

/**
 * @Author: LiFly
 * @Date: 2024/8/27 17:05
 * @Description:
 */
@Configuration
public class CaptchaConfig {

    private static final String CHAR_LENGTH = "4";

    private static final String CHAR_SPACE = "8";

    private static final String CAPTCHA_NOISE_IMPL = "com.google.code.kaptcha.impl.NoNoise";

    private static final String CAPTCHA_SCARIFICATION_IMPL = "com.google.code.kaptcha.impl.WaterRipple";

    private static final String KAPTCHA_TEXTPRODUCER_CHAR_STRING = "0123456789";

    /**
     * 验证码配置
     * @return 获取默认验证码配置
     */
    @Bean
    @Qualifier("captchaProducer")
    public DefaultKaptcha kaptcha(){
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        //验证码个数
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH,CHAR_LENGTH);
        //字体间隔
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE,CHAR_SPACE);

        //干扰实现类
        properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, CAPTCHA_NOISE_IMPL);

        //图片样式
        properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, CAPTCHA_SCARIFICATION_IMPL);

        //文字来源
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING,KAPTCHA_TEXTPRODUCER_CHAR_STRING);

        Config config = new Config(properties);

        kaptcha.setConfig(config);

        return kaptcha;
    }
}

配置redis的序列化方式

/**
 * @Author: LiFly
 * @Date: 2024/8/27 16:39
 * @Description:
 */
@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用Jackson2JsonRedisSerialize替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        //设置key和value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //设置支持事务
        redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

redis相关链接配置也加上

spring.redis.host=127.0.0.1
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active = 10
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle = 10
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle = 0
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait= -1ms
#指定客户端
spring.redis.client-type = lettuce
#配置文件指定缓存类型
spring.cache.type=redis

下面我们来开发获取验证码的接口

 @ApiOperation("获取验证码")
    @GetMapping("/getCaptcha")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
        //创建验证码
        String captchaText = captchaProducer.createText();
        //缓存验证码
        redisTemplate.opsForValue().set(getCaptchaKey(request), captchaText,CAPTCHA_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        //创建图形验证码
        BufferedImage bufferedImage = captchaProducer.createImage(captchaText);
        //返回图形验证码
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            ImageIO.write(bufferedImage,"jpg",out);
            out.flush();
            out.close();
        }catch (Exception e){
            log.error("获取图形验证码失败!e={}",e.getMessage());
        }
    }

 /**
     * 获取缓存key
     * @param request 请求参数
     * @return 组装key
     */
    private String getCaptchaKey(HttpServletRequest request){
        String ip = CommonUtil.getIpAddr(request);
        String userAgent = request.getHeader("User-Agent");
        return "user-service:captcha:" + CommonUtil.MD5(ip+userAgent);
    }

获取验证码的接口非常简单,调用验证码配置类获取验证码,将获取到的验证码缓存到redis中并设置过期时间,然后调用配置类传入验证码获取到图形验证码,通过IO流返回。下面校验验证码

   @ApiOperation("校验验证码")
    @PostMapping("/sendCode")
    public JsonData sendCode(@RequestParam(value = "to",required = true)String to,
                             @RequestParam(value = "captcha",required = true) String captcha,
                             HttpServletRequest request) {
        String key = getCaptchaKey(request);
        String cacheCaptcha = redisTemplate.opsForValue().get(key);

        if (cacheCaptcha != null && captcha != null && cacheCaptcha.equals(captcha)) {
            redisTemplate.delete(key);
            //发送验证码
            return JsonData.buildSuccess();
        }else {
            return JsonData.buildResult("图形验证码错误");
        }

    }

获取前端传过来的手机号以及图形验证码,根据ip以及网络地址相关西信息获取缓存key,根据key获取验证码,然后比较验证码是否一致,如果一致验证通过,删除缓存,执行发送短信相关业务。如果不一致直接返回给前端错误信息。

接口测试

下面我们通过postman来测试下开发的接口,首先获取验证码:
在这里插入图片描述
查看到获取的验证码为5437,再去查看下redis是否存在:
在这里插入图片描述
redis也存在刚才获取的验证码,说明我们获取验证码的接口是没问题的。
再去看下校验验证码的这个接口,
在这里插入图片描述
可以看到,校验验证码的接口也是没问题的,校验验证码后,里面会删除缓存,这时候再来看下缓存中是否还存在:
在这里插入图片描述
刷新缓存为空了,说明检验验证码的接口逻辑是没问题的。后续就可以执行发送短信验证码了。
上述我们主要讲述了用户登录获取验证码的逻辑,用户注册,找回密码逻辑都是差不多一样的,只是后续的处理不太一样,在这里就不再过多讲述了。
总之,图形验证码与手机号验证码的结合使用,不仅增强了系统的安全性,也为用户提供了便捷的操作体验。未来,随着更多创新技术的应用,验证码系统将会变得更加智能和人性化,更好地服务于广大用户。希望本文能为开发者们提供一些有价值的参考,帮助大家在实际工作中构建更加稳固的安全防线。
更多精彩内容请关注以下公众号
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Unity3D在2D游戏中获取触屏物体的方法

我们的需求是&#xff1a; 假如屏幕中一个棋盘&#xff0c;每个棋子是button构成的&#xff0c;我们希望手指或者鼠标在哪里&#xff0c;就显示那个位置的button信息。 网上有很多获取触屏物体信息的信息的方法如下面代码所示&#xff1a; Camera cam Camera.main; // pre-de…

P11019 「LAOI-6」[太阳]] 请使用最新版手机 QQ 体验新功能

English statement. You must submit your code at the Chinese version of the statement. 题目描述 你的 QQ 收到了一条新消息&#xff01;但是你很生气&#xff0c;因为你看不到别人在手机 QQ 上发送的超级表情。 消息形如一个字符串 S&#xff0c;包含且仅包含一个超级表…

注册安全分析报告:熊猫频道

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

LTE PSS主同步信号搜索 MATLAB实现

本期带来PSS相关检测说明和MATLAB实现&#xff0c;本期只讲相关方面的&#xff0c;所以MATLAB实现也是相关的部分&#xff0c;频偏估计方面的待下期开讲。 LTE 4G PSS搜索分为TDD搜索和FDD搜索&#xff0c;但是对于 TDD 和 FDD 而言&#xff0c;PSS同步信号的结构是完全一样的&…

探索Python的数学魔法:Numpy库的神秘力量

文章目录 探索Python的数学魔法&#xff1a;Numpy库的神秘力量背景&#xff1a;为什么选择Numpy&#xff1f;Numpy是什么&#xff1f;如何安装Numpy&#xff1f;五个简单的库函数使用方法场景应用常见Bug及解决方案总结 探索Python的数学魔法&#xff1a;Numpy库的神秘力量 背景…

springnboot +uniapp汽车租赁系统

springnboot uniapp汽车租赁系统 手机移动端&#xff1a;主页&#xff0c;租赁汽车展示&#xff0c;汽车租赁&#xff0c;我的租赁记录&#xff0c;还车记录&#xff0c;注册登录&#xff0c;修改个人资料 PC端管理后台&#xff1a;公告管理&#xff0c;用户管理&#xff0c;…

PMBOK® 第六版 项目进度管理知识领域

目录 读后感—PMBOK第六版 目录 提到“进度”&#xff0c;大家都能直观理解其含义。有统计指出&#xff0c;现在项目的延迟交付比例越来越高&#xff0c;并且这一趋势持续上升。造成项目延期的原因是多方面的&#xff0c;常见的原因之一是在项目初期&#xff0c;由于时间充裕&…

Java:方法的使用

一.什么是方法&#xff1a; 在 Java SE 中&#xff0c;方法是类中包含的功能块&#xff0c;它定义了一组执行某些操作的指令。方法用于封装可重复的代码逻辑&#xff0c;并使代码更简洁、易读和易维护。&#xff08;类似于 C 语言中的 "函数"&#xff09; 二.方法的…

JavaFX基本控件-Button

JavaFX基本控件-Button 常用属性textpaddingalignmenttextAlignmentwidthheighttooltipborderwrapTextellipsisStringunderlinegraphicgraphicTextGapdisable 实现方式Java实现fxml实现 常用属性 text 设置文本内容 button.setText("测试按钮");padding 内边距 butt…

css 个人喜欢的样式 速查笔记

起因&#xff0c; 目的: 记录自己喜欢的&#xff0c; 觉得比较好看的 css. 下次用的时候&#xff0c;直接复制&#xff0c;很方便。 1 设置英语字体: Noto html <link rel"preconnect" href"https://fonts.googleapis.com"> <link rel"p…

[情商-12]:人际交流中倾听的三大层次,十二个小层次, 低情商摆样子;研发人员关注:逻辑层;高情商人关注:情绪、情感、需求、动机

目录 前言&#xff1a; 一、基础层次 二、进阶层次 三、高级层次 总结 前言&#xff1a; 倾听是一个复杂而多维的过程&#xff0c;它不仅仅是耳朵在工作&#xff0c;更是心灵和思维的深度参与。根据不同的理论和观察角度&#xff0c;倾听可以划分为不同的层次。以下是对倾…

AI文献综述神器,有这一款就够了!

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 当前的AI辅助文献综述的工具有很多&#xff0c;如果说功能最强大的&#xff0c;娜姐无疑要推SciSpace了。 SciSpace利用强大的AI算法&#xff0c;理解并建立研究论文之间的联…

一篇带你速通差分算法(C/C++)

个人主页&#xff1a;摆烂小白敲代码 创作领域&#xff1a;算法、C/C 持续更新算法领域的文章&#xff0c;让博主在您的算法之路上祝您一臂之力 欢迎各位大佬莅临我的博客&#xff0c;您的关注、点赞、收藏、评论是我持续创作最大的动力 差分算法是一种在计算机科学中常用的算法…

C# 通过拖控件移动窗体

目录 引言一、通过控件事件移动窗体1、创建窗体界面2、添加控件事件3、添加代码 二、通过windowsAPI移动窗体1、 构建窗体和添加事件2、代码展示 引言 在C#Form窗体设计中&#xff0c;如果我们不需要使用默认边框设计自己个性化的窗体&#xff08;FromBorderStylenone时&#…

YOLOv8改进 | 模块缝合 | C2f 融合RFCAConv增强感受野空间特征 【二次融合 小白必备】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…

《师兄啊师兄年番2》震撼发布,玄机科技再铸国漫巅峰!

随着秋风送爽&#xff0c;9月6日的优酷动漫发布会无疑为所有动漫爱好者带来了一场视觉与心灵的双重盛宴。其中最引人瞩目的&#xff0c;莫过于《师兄啊师兄年番2》携其全新海报的惊艳亮相&#xff0c;不仅预示着这部备受期待的国漫巨制即将开启全新篇章&#xff0c;更以其独特的…

SpringBoot3.x+MyBatisPlus+druid多数据源配置

1 引言 本章主要介绍SpringBoot3.x多数据源配置&#xff0c;以及在此基础上配置分页拦截&#xff0c;自动填充功等功能&#xff0c;源码链接在文章最后。下面列出几个重要文件进行介绍。 2 项目结构 整体项目结构如下&#xff0c;主要介绍配置文件和配置类。 3 主要代码 …

IstoreOS内网域名解析 修改网页端口

设置了自定义DNS&#xff0c;解析到Istore的IP&#xff0c;安装了1Panel&#xff0c;想用1Panel做域名解析&#xff0c;但是需要80和443端口。 这俩端口被IstoreOS占用了&#xff0c;下面修改 修改网页端口 https://github.com/istoreos/istoreos/issues/47 opkg update opkg…

【每日刷题】Day112

【每日刷题】Day112 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 2. 面试题 08.01. 三步问题 - …

Web

关于Web Web是基于HTTP协议进行交互的应用网络Web就是通过使用浏览器/APP访问的各种资源 一个请求对应一个响应 eg. 淘宝网 输入一个url&#xff0c;就会返回一个页面 简单的网站开发 简单代码 package mainimport ("fmt""net/http" )/*http.ResponseWr…