前后端分离java开发图形验证码+谷歌开源Kaptcha使用(Springboot+redis实现图形验证码校验)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 前后端分离java开发图形验证码+谷歌开源Kaptcha使用(Springboot+redis实现图形验证码校验)

1.背景

注册-登录-修改密码一般需要发送验证码,但是容易被攻击恶意调用。

1.1 什么是短信-邮箱轰炸机

手机短信轰炸机是批量、循环给手机无限发送各种网站的注册验证码短信的方法。

1.2 公司带来的损失

短信一条5分钱,如果被大盗刷大家自己计算邮箱通知不用钱,但被大盗刷,带宽、连接等都被占用,导致无法正常使用。

2.如何避免自己的网站成为"肉鸡“或者被刷呢

  • 增加图形验证码(开发人员)
    图形验证码(CAPTCHA),是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称。其本质是-种区分用户是计算机还是人的公共全自动程序。可以有效的防止某些特定程序以暴力方式不断进行登录尝试。验证码的不断发展其实是随着其破解功能的逐步强大而跟着演进的,这是一个攻防博弈的精彩世界。
  • 单IP请求次数限制(开发人员)
  • 限制号码发送(一般短信提供商会做)
  • 攻防永远是有的,只过加大了攻击者的成本,ROI划不过来自然就放弃了

3.表单重复提交问题

  1. 用户正常提交,由于网络延迟等原因,未收到服务器的响应,这时,用户着急多点了几次提交操作,会造成表单重复提交。
  2. 用户正常提交,服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交,也会造成表单重复提交。

这两个表单重复提交问题的解决方案:验证码

4.Kaptcha框架介绍

谷歌开源的一个可高度配置的实用验证码生成工具:

  • 验证码的字体/大小颜色
  • 验证码内容的范围(数字,字母,中文汉字!)
  • 验证码图片的大小,边框,边框粗细,边框颜色
  • 验证码的干扰线验证码的样式(鱼眼样式、3D、 普通模糊)

5.前后端分离验证码实战开发-思路分析

这里以修改密码为例,规则是修改用户密码必须一同将验证码传给后端,如果验证码失效或者错误都需要重新生成并获取验证码。

  1. 前端向后端请求验证码。
  2. 后端通过谷歌开源工具Kaptcha生成图形验证码(实际是5个随机字符),缓存到redis,key键可以是 业务+用户id,value值就是那5个随机字符。设置TTL为2分钟。
  3. 后端将图形验证码转base64编码字符串,将该字符串返回给前端。
  4. 前端解析base64编码的字符串,即可在页面上显示图形验证码。
  5. 用户输入密码与验证码后提交表单到后端。
  6. 后端根据业务和用户id组成的key键到redis查找缓存的验证码信息,会有如下情况:
  • 如果redis返回为空,则通知前端验证码失效,需要重新获取验证码。
  • 如果redis返回不为空,但是不相等,说明验证码输入错误。则删除redis中对应验证码缓存,通知前端验证码错误,需要重新获取验证码。
  • 如果redis返回不为空,并且相等,则校验成功,就删除redis中对应验证码缓存,并在mysql中修改密码。最后通知前端修改成功。

6.前后端分离验证码实战开发-后端代码

5.1 pom.xml核心依赖

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

5.2 KaptchaConfig配置类

package com.zhulang.waveedu.sms.config;
import com.google.code.kaptcha.Producer;
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;
/**
 * 谷歌提供的图片验证码kaptcha
 *
 * @author 狐狸半面添
 * @create 2023-01-29 11:02
 */
@Configuration
public class KaptchaConfig {
    @Bean
    public Producer kaptcha() {
        Properties properties = new Properties();
        /*
            设置图片有边框并且为蓝色
            properties.setProperty("kaptcha.border", "no");
            properties.setProperty("kaptcha.border.color", "blue");
         */
        // 设置图片无边框
        properties.setProperty("kaptcha.border", "no");
        // 背景颜色渐变开始,这里设置的是rgb值156,156,156
        properties.put("kaptcha.background.clear.from", "156,156,156");
        // 背景颜色渐变结束,这里设置以白色结束
        properties.put("kaptcha.background.clear.to", "white");
        // 字体颜色,这里设置为黑色
        properties.put("kaptcha.textproducer.font.color", "black");
        // 文字间隔,这里设置为10px
        properties.put("kaptcha.textproducer.char.space", "10");
        /*
            如果需要去掉干扰线,则如此配置:
            properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
         */
        // 干扰线颜色配置,这里设置成了idea的Darcula主题的背景色
        properties.put("kaptcha.noise.color", "43,43,43");
        // 字体
        properties.put("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        // 图片宽度,默认也是200px
        properties.setProperty("kaptcha.image.width", "200");
        // 图片高度,默认也是50px
        properties.setProperty("kaptcha.image.height", "50");
        // 从哪些字符中产生
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789abcdefghijklmnopqrsduvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
        // 字符个数
        properties.setProperty("kaptcha.textproducer.char.length", "5");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

5.3 CaptchaServiceController生成与获取验证码

  • RedisCacheUtils是一个自定义的redis缓存工具类
  • RedisConstants是一个redis常量类,主要是设置缓存key前缀与TTL
  • UserHolderUtils.getUserId():通过自定义UserHolderUtils工具类获取用户id(通过token从redis获取用户信息,包括了userId)
package com.zhulang.waveedu.sms.controller;
import com.google.code.kaptcha.Producer;
import com.zhulang.waveedu.common.constant.RedisConstants;
import com.zhulang.waveedu.common.entity.Result;
import com.zhulang.waveedu.common.util.RedisCacheUtils;
import com.zhulang.waveedu.common.util.UserHolderUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
/**
 * 生成图片验证码
 *
 * @author 狐狸半面添
 * @create 2023-01-29 17:35
 */
@RestController
@RequestMapping("/captcha")
public class CaptchaServiceController {
    @Resource
    private Producer producer;
    @Resource
    private RedisCacheUtils redisCacheUtils;
    /**
     * 获取修改密码的图片验证码base64编码
     * 并缓存至redis中
     *
     * @return base编码
     */
    @GetMapping("/pwd/vc.jpg")
    public Result getPwdCaptcha(){
        // 1.生成验证码字符
        String text = producer.createText();
        // 2.生成图片
        BufferedImage bi = producer.createImage(text);
        FastByteArrayOutputStream fos = new FastByteArrayOutputStream();
        try {
            ImageIO.write(bi, "jpg", fos);
            // 3.缓存至 redis 中
            redisCacheUtils.setCacheObject(RedisConstants.PWD_CODE_KEY+ UserHolderUtils.getUserId(),text,RedisConstants.PWD_CODE_TTL);
            // 4.返回验证码图片的base64编码
            String imgEncode = Base64.encodeBase64String(fos.toByteArray());
            fos.flush();
            return Result.ok(imgEncode);
        }catch (Exception e){
            return Result.error();
        }finally {
            fos.close();
        }
    }
}

5.4 修改密码验证码校验代码

package com.zhulang.waveedu.basic.vo;
import com.zhulang.waveedu.common.util.RegexUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
 * 修改密码时的VO
 *
 * @author 狐狸半面添
 * @create 2023-01-29 22:09
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UpdatePwdVO {
    /**
     * 第一次输入的密码
     */
    @NotBlank(message = "输入密码为空")
    @Pattern(regexp = RegexUtils.RegexPatterns.PASSWORD_REGEX, message = "密码格式错误,应为8-16位的数字或字母")
    private String firPassword;
    /**
     * 第二次输入的密码(确认密码)
     */
    @NotBlank(message = "输入密码为空")
    @Pattern(regexp = RegexUtils.RegexPatterns.PASSWORD_REGEX, message = "密码格式错误,应为8-16位的数字或字母")
    private String secPassword;
    /**
     * 图片验证码字符
     */
    @NotBlank(message = "验证码为空")
    @Pattern(regexp = RegexUtils.RegexPatterns.CAPTCHA_CODE_REGEX, message = "验证码格式错误")
    private String code;
}
/**
 * User登录,注册,推出登录,注销的控制器
 *
 * @author 狐狸半面添
 * @create 2023-01-17 23:14
 */
@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 修改密码
     *
     * @param updatePwdVO 两个密码+code
     * @return 修改情况
     */
    @PutMapping("/updatePwd")
    public Result updatePwd(@Validated @RequestBody UpdatePwdVO updatePwdVO){
        return userService.updatePwd(updatePwdVO);
    }
}
/**
 * ServiceImpl实现了IService,提供了IService中基础功能的实现
 * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
 *
 * @author 狐狸半面添
 * @create 2023-01-17 23:31
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private RedisCacheUtils redisCacheUtils;
    @Override
    public Result updatePwd(UpdatePwdVO updatePwdVO) {
        // 1.如果两个密码不一致,则返回error
        if (!updatePwdVO.getFirPassword().equals(updatePwdVO.getSecPassword())){
            return Result.error(HttpStatus.HTTP_VERIFY_FAIL.getCode(), "两次密码不一致");
        }
        // 2.校验验证码是否正确
        String code = redisCacheUtils.getCacheObject(RedisConstants.PWD_CODE_KEY + UserHolderUtils.getUserId());
        // 2.1 不存在则返回
        if (code==null){
            return Result.error(HttpStatus.HTTP_VERIFY_FAIL.getCode(),"验证码已失效,请重新获取");
        }
        // 2.2 存在但不一致则清除并返回(忽略大小写)
        if (!code.equalsIgnoreCase(updatePwdVO.getCode())){
            redisCacheUtils.deleteObject(RedisConstants.PWD_CODE_KEY + UserHolderUtils.getUserId());
            return Result.error(HttpStatus.HTTP_VERIFY_FAIL.getCode(),"验证码错误,请重新获取");
        }
        // 3.验证码正确,则从缓存中移除
        redisCacheUtils.deleteObject(RedisConstants.PWD_CODE_KEY + UserHolderUtils.getUserId());
        // 4.修改密码
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(User::getId,UserHolderUtils.getUserId())
            .set(User::getPassword,PasswordEncoderUtils.encode(updatePwdVO.getFirPassword()));
        this.update(wrapper);
        // 5.返回
        return Result.ok();
    }
}

5.5 测试图片验证码生成

我们对这段Base64进行解码,这里就使用一个在线工具:base64图片在线转换工具 - 站长工具 (chinaz.com)

📍 注意需要在上图中得到的字符串前面指定转换格式:data:image/jpg;base64,

7.验证码未来发展的讨论

验证码技术其实是一个攻防博弈的动态发展的技术,因此,随着验证码发展得越来越安全,相应的破解技术也跟着不断发展,随之而来的,是双方的

资源成本越来越高。

例如对于互联网应用来说,商业验证码、短信验证码等这些验证码,如果应用的业务流程控制不好,很容易被羊毛党、黑客等人利用,造成成本极大浪费。而网上逐渐出现的各种奇葩验证码,人眼难辨,也让很多正常用户叫苦不迭。

对于破解方来说,随着机器学习以及人工打码等技术的不断发展,可选择的技术手段也越来越多。验证码技术,也在破解方的大量资源投入下,变得越来越鸡肋。并且,很多互联网应用逐渐复杂的验证方式,安全性提高的同时,也提高了很多正常用户的使用门槛,成为了各种电信诈骗的温床。验证码技术逐渐开始偏离了最初的初衷。

而未来的验证码技术,一方面,会通过引入更多的验证元素来进一步提高验证码的安全性,例如刷脸、刷指纹、语音交互、点选你购买过的商品等。另一方面,通过对行为式验证码的研究逐渐深入,可以从传统的面向结果的验证,转化成面向过程的行为验证,并逐渐减少人工参与,降低关键信息被劫持的风险,形成更多对用户无感知的验证方式。例如分析用户鼠标轨迹、按键频率、使用习惯等。

总之,验证码技术,是一个所有人都将亲身参与的精彩世界。


相关文章
|
3月前
|
Java 数据安全/隐私保护
快手小红书抖音留痕工具,自动留痕插件工具,java代码开源
这个框架包含三个核心模块:主操作类处理点赞评论、配置管理类和代理管理类。使用时需要配合
|
22天前
|
人工智能 Java 开发者
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
JManus是阿里开源的Java版OpenManus,基于Spring AI Alibaba框架,助力Java开发者便捷应用AI技术。支持多Agent框架、网页配置、MCP协议及PLAN-ACT模式,可集成多模型,适配阿里云百炼平台与本地ollama。提供Docker与源码部署方式,具备无限上下文处理能力,适用于复杂AI场景。当前仍在完善模型配置等功能,欢迎参与开源共建。
629 58
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
|
5月前
|
人工智能 Kubernetes Java
回归开源,两位 Java 和 Go 程序员分享的开源贡献指引
Higress是一个基于Istio和Envoy的云原生API网关,支持AI功能扩展。它通过Go/Rust/JS编写的Wasm插件提供可扩展架构,并包含Node和Java的console模块。Higress起源于阿里巴巴,解决了Tengine配置重载及gRPC/Dubbo负载均衡问题,现已成为阿里云API网关的基础。本文介绍Higress的基本架构、功能(如AI网关、API管理、Ingress流量网关等)、部署方式以及如何参与开源贡献。此外,还提供了有效的开源贡献指南和社区交流信息。
530 33
|
2月前
|
Java Linux Apache
Apache NetBeans 27 (macOS, Linux, Windows) - Java 等多语言开源跨平台 IDE
Apache NetBeans 27 (macOS, Linux, Windows) - Java 等多语言开源跨平台 IDE
150 5
Apache NetBeans 27 (macOS, Linux, Windows) - Java 等多语言开源跨平台 IDE
|
22天前
|
消息中间件 人工智能 Java
抖音微信爆款小游戏大全:免费休闲/竞技/益智/PHP+Java全筏开源开发
本文基于2025年最新行业数据,深入解析抖音/微信爆款小游戏的开发逻辑,重点讲解PHP+Java双引擎架构实战,涵盖技术选型、架构设计、性能优化与开源生态,提供完整开源工具链,助力开发者从理论到落地打造高留存、高并发的小游戏产品。
|
6月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
433 7
|
28天前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
5月前
|
人工智能 Java 程序员
JManus - 面向 Java 开发者的开源通用智能体
JManus 是一个以 Java 为核心、完全开源的 OpenManus 实现,隶属于 Spring AI Alibaba 项目。它旨在让 Java 程序员更便捷地使用 AI 技术,支持多 Agent 框架、网页配置 Agent、MCP 协议和 PLAN-ACT 模式。项目在 GitHub 上已获近 3k star,可集成多个大模型如 Claude 3.5 和 Qwen3。开发者可通过 IDE 或 Maven 快速运行项目,体验智能问答与工具调用功能。欢迎参与开源共建,推动通用 AI Agent 框架发展。
8566 64
|
6月前
|
人工智能 Java 数据库
飞算 JavaAI:革新电商订单系统 Spring Boot 微服务开发
在电商订单系统开发中,传统方式耗时约30天,需应对复杂代码、调试与测试。飞算JavaAI作为一款AI代码生成工具,专注于简化Spring Boot微服务开发。它能根据业务需求自动生成RESTful API、数据库交互及事务管理代码,将开发时间缩短至1小时,效率提升80%。通过减少样板代码编写,提供规范且准确的代码,飞算JavaAI显著降低了开发成本,为软件开发带来革新动力。
|
7月前
|
缓存 NoSQL Java
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
553 79