SpringBoot+原生awt,实现花花绿绿的图形验证码

news2024/11/28 9:42:09

图形验证码是用于验证用户身份的一种方式,通常在网站注册、登录或进行某些敏感操作时会使用。它通过展示一个包含随机字符或数字的图形,要求用户输入相应的字符或数字来证明其为真人而非机器人。图形验证码能有效地防止机器人攻击和恶意注册行为,提高网站的安全性。

本文将基于 SpringBoot 和原生的 Java awt 包,完成图形验证码的实现,源码在 项目仓库 中,需要者可自助参考。

  • 一、导入依赖
  • 二、编写工具类
    • 2.1 验证码配置
    • 2.2 生成随机数
    • 2.3 生成随机颜色
    • 2.4 编写创建图片方法
    • 2.5 编写构建函数
  • 三、编写接口
    • 3.1 验证码初始化
    • 3.2 图片返回
    • 3.3 验证码过滤

一、导入依赖

在实现图形验证码之前,首先要导入依赖,比如 SpringBoot 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

awt 包属于 Java 原生包,无需导入三方 maven 依赖,只需引入 JDK 即可。

二、编写工具类

接下来,就开始开发验证码的工具类,包括验证码配置、生成随机数、生成随机颜色、编写创建图片方法、编写构建函数,下面将逐一介绍。

2.1 验证码配置

首先是验证码的配置,创建 CreateVerifyCode 类,创建相应字段,包括字符个数、图片高度、图片宽度、干扰个数等参数,定义如下。

@ApiModelProperty(value = "验证码字符个数")
private int charactersNumber = 4;

@ApiModelProperty(value = "图片高度")
private int imagePeripheralHeight = 40;

@ApiModelProperty(value = "图片宽度")
private int imagePeripheralWidth = 160;

@ApiModelProperty(value = "干扰线数")
private int lineCount = 20;

2.2 生成随机数

新建一个方法 randomStr,用于实现生成随机数的功能,代码如下。

@ApiOperation(value = "随机生成验证码")
public String randomStr(int size) {
    String str1 = "0123456789";
    String str2 = "";
    for (int i = 0; i < size; i++) {
        double randomIndex = Math.random();
        double randomNumber = randomIndex * (str1.length() - 1);
        str2 += str1.charAt((int) randomNumber);
    }
    return str2;
}

2.3 生成随机颜色

新建一个方法 getRandColor,用于实现生成随机颜色的功能,代码如下。

@ApiOperation(value = "随机生成验证码颜色")
private Color getRandColor(int color1, int color2) {
    color1 = color1 > 255 ? 255 : color1;
    color2 = color2 > 255 ? 255 : color2;
    return new Color(color1 + random.nextInt(color2 - color1), color1 + random.nextInt(color2 - color1), color1 + random.nextInt(color2 - color1));
}

2.4 编写创建图片方法

有了验证码配置之后,新建一个方法 creatImage,用于实现图形验证码图片创建的逻辑,代码如下。

@ApiOperation(value = "图片生成工具类")
private void creatImage(String code) {
    if(ZwzNullUtils.isNull(code)){
        throw new ZwzException("图形验证码过期了,再生成个新的哦!");
    }
    this.code = code;
    buffImg = new BufferedImage(imagePeripheralWidth, imagePeripheralHeight, BufferedImage.TYPE_INT_RGB);
    Graphics g = buffImg.getGraphics();
    g.setColor(getRandColor(200, 250));
    g.fillRect(0, 0, imagePeripheralWidth, imagePeripheralHeight);
    Font font = new Font("Fixedsys", Font.BOLD, imagePeripheralHeight - 5);
    g.setFont(font);
    float yawpRate = 0.01f;
    int area = (int) (yawpRate * imagePeripheralWidth * imagePeripheralHeight);
    for (int i = 0; i < area; i++) {
        buffImg.setRGB(random.nextInt(imagePeripheralWidth), random.nextInt(imagePeripheralHeight), random.nextInt(255));
    }
    for (int i = 0; i < lineCount; i++) {
        int xs = random.nextInt(imagePeripheralWidth);
        int ys = random.nextInt(imagePeripheralHeight);
        int xe = xs + random.nextInt(imagePeripheralWidth);
        int ye = ys + random.nextInt(imagePeripheralHeight);
        g.setColor(getRandColor(2, 254));
        g.drawLine(xs, ys, xe, ye);
    }
    for (int i = 0; i < code.length(); i++) {
        String strRand = code.substring(i, i + 1);
        g.setColor(getRandColor(2, 254));
        g.drawString(strRand, i * (imagePeripheralWidth / charactersNumber) + 3, imagePeripheralHeight - 8);
    }
}

2.5 编写构建函数

为了用尽可能精简的代码实现图形验证码,构造函数是比不可少的,开发者可以在构建对象时直接传入参数,实现图形验证码的设计,代码如下。

public CreateVerifyCode(int imageWidth, int imageHeight, int codeCount, int lineCount, String code) {
    this.imagePeripheralWidth = imageWidth;
    this.imagePeripheralHeight = imageHeight;
    this.charactersNumber = codeCount;
    this.lineCount = lineCount;
    creatImage(code);
}

三、编写接口

工具类编写完成后,就开始设计 API 接口了,可以分为验证码初始化和图片返回。

3.1 验证码初始化

首先定义一个 init 接口,用户请求该接口时,系统利用 Java 的 UUID,生成一个随机字符串,并随机生成一个四位数字,放入缓存,返回该随机字符串,代码如下。

@RequestMapping(value = "/init", method = RequestMethod.GET)
@ApiOperation(value = "初始化验证码")
public Result<Object> init() {
    String codeId = UUID.randomUUID().toString().replace("-","");
    redisTemplate.opsForValue().set(codeId, new CreateVerifyCode().randomStr(4),2L, TimeUnit.MINUTES);
    return ResultUtil.data(codeId);
}

接口测试结果如下,接口返回了一个随机字符串bdb3cc192cf147eda20afa5e5d22bd8c

在这里插入图片描述

3.2 图片返回

前端收到随机字符串 bdb3cc192cf147eda20afa5e5d22bd8c 后,再次请求,拿走验证码图片,核心代码如下。

@RequestMapping(value = "/draw/{captchaId}", method = RequestMethod.GET)
@ApiOperation(value = "根据验证码ID获取图片")
public void draw(@PathVariable("captchaId") String captchaId, HttpServletResponse response) throws IOException {
    String codeStr = redisTemplate.opsForValue().get(captchaId);
    CreateVerifyCode createVerifyCode = new CreateVerifyCode(116,36,4,10, codeStr);
    response.setContentType("image/png");
    createVerifyCode.write(response.getOutputStream());
}

首先根据随机字符串,在 redis 中拿到验证码的值,在调用验证码工具类生成图片,返回前端。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.3 验证码过滤

系统集成了 Spring Security,需要引入并重写 WebSecurityConfig 类,重写 securityFilterChain 方法。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    http.authorizeHttpRequests().requestMatchers("/zwz/dictData/getByType/**","/zwz/file/view/**","/zwz/user/regist","/zwz/common/**","/*/*.js","/*/*.css","/*/*.png","/*/*.ico", "/swagger-ui.html").permitAll()
            .and().formLogin().loginPage("/zwz/common/needLogin").loginProcessingUrl("/zwz/login").permitAll()
            .successHandler(authenticationSuccessHandler).failureHandler(authenticationFailHandler).and()
            .headers().frameOptions().disable().and()
            .logout()
            .permitAll()
            .and()
            .authorizeHttpRequests()
            .anyRequest()
            .authenticated()
            .and()
            .cors().and()
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .exceptionHandling().accessDeniedHandler(zwzAccessDeniedHandler)
            .and()
            .authenticationProvider(authenticationProvider())
            .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(imageValidateFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

对于通过验证码的请求,给与放行,核心代码如下:

@Override
@ApiOperation(value = "验证码过滤")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    Boolean filterFlag = false;
    for(String requestURI : captchaProperties.getVerification()){
        if(pathMatcher.match(requestURI, request.getRequestURI())){
            filterFlag = true;
            break;
        }
    }
    if(!filterFlag) {
        filterChain.doFilter(request, response);
        return;
    }
    String verificationCodeId = request.getParameter("captchaId");
    String userInputCode = request.getParameter("code");
    if(ZwzNullUtils.isNull(userInputCode) || ZwzNullUtils.isNull(verificationCodeId)){
        ResponseUtil.out(response, ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_CODE_FAIL_CODE,"验证码为空"));
        return;
    }
    String codeAnsInRedis = redisTemplate.opsForValue().get(verificationCodeId);
    if(ZwzNullUtils.isNull(codeAnsInRedis)){
        ResponseUtil.out(response, ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_CODE_FAIL_CODE,"已过期的验证码,需要重新填写"));
        return;
    }
    if(!Objects.equals(codeAnsInRedis.toLowerCase(),userInputCode.toLowerCase())) {
        ResponseUtil.out(response, ResponseUtil.resultMap(RESPONSE_FAIL_FLAG,RESPONSE_CODE_FAIL_CODE,"验证码不正确"));
        return;
    }
    redisTemplate.delete(verificationCodeId);
    filterChain.doFilter(request, response);
}

最终,基于 SpringBoot+原生 awt,实现花花绿绿的图形验证码。

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

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

相关文章

Excel·VBA自动生成日记账的对方科目

如图&#xff1a;根据日记账/序时账的日期、凭证号为一组&#xff0c;按借贷方向生成相反的科目&#xff0c;并写入H列。可能存在一对一、一对多、多对多等情况的账目 目录 数组法遍历、判断、写入测试结果 多对多问题处理测试结果 数组法遍历、判断、写入 适用日期凭证号连续…

HTTPS的加密流程——巨详细!

文章目录 前言HTTPS的工作过程引入对称加密引入非对称加密引入证书完整的加密流程总结 前言 HTTPS 也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层. HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在传输过程中出现一些被篡改的情况. 比如&#xff1a;臭…

民宿预订系统的设计与实现(ASP.NET,SQLServer)

这个民宿预订系统是由第三方的运营公司来运营&#xff0c;他提供了一个民宿和客户都使用的一个信息平台&#xff0c;民宿注册之后把自己的民宿信息发布到网站平台上&#xff0c;然后发布自己的房间信息&#xff0c;打折信息等供客户查看和选择。客户可以在网站平台上查看民宿信…

深度学习:大模型的正则化

l1l2正则和dropout正则化[https://youzipi.blog.csdn.net/article/details/75307522] LN和BN归一化 [深度学习:批归一化Batch Normalization] 主流大模型使用的Normalization主要有三类,分别是Layer Norm,RMS Norm,以及Deep Norm。 Post-Norm和Pre-Norm 根据Normalizat…

网工内推 | 快手、瑞芯微招运维,思科、红帽认证优先

01 快手 招聘岗位&#xff1a;IT系统运维 职责描述&#xff1a; 1、负责IT基础架构运维体系的建设和优化改进&#xff1b; 2、负责IT核心基础服务&#xff08;如DNS、负载均衡、容器&#xff09;的架构设计、平台建设和运维&#xff1b; 3、负责IT内部日志系统、监控系统、报警…

SpringCloud微服务框架(通俗易懂,一秒上手)

&#x1f381;&#x1f381;资源&#xff1a;https://pan.baidu.com/s/1zRmwSvSvoDkWh0-MynwERA&pwd1234 SpringCloud微服务框架 &#xff08;一&#xff09;认识微服务服务架构演变SpringCloud &#xff08;二&#xff09;微服务拆分案例服务拆分服务间调用 &#xff08;三…

ROS:订阅者Subscriber的编程实现(C++)

目录 一、话题模型二、创建功能包三、创建Subscriber代码四、编译代码五、运行 一、话题模型 图中&#xff0c;我们使用ROS Master管理节点。 有两个主要节点&#xff1a; Publisher&#xff0c;名为Turtle Velocity&#xff08;即海龟的速度&#xff09; Subscriber&#xff0…

Rocketmq面试(一) Rocketmq同一个消费组订阅不同的Tag,会有什么问题?

先说结果&#xff1a;会造成数据丢失 再说依据&#xff1a; RocketMQ要求同一个消费者组内的消费者必须订阅关系一致&#xff0c;如果订阅关系不一致会出现消息丢失的问题。 官网入口&#xff1a;订阅关系一致 | RocketMQ 不想看官网的&#xff0c;直接看结论 什么叫订阅关…

复杂SQL实践-MYSQL

MySQL 8.0窗口函数 MySQL从8.0版本开始支持窗口函数。 窗口函数总体上可以分为序号函数, 分布函数, 前后函数, 首尾函数和其他函数。 描述 题目&#xff1a;现在运营想要查看用户在某天刷题后第二天还会再来刷题的平均概率。请你取出相应数据。 示例1 drop table if exist…

对远程http服务的拨测体验

背景&#xff1a; 过程是这样的&#xff0c;需要与合作方数据进行交互&#xff08;肯定是不允许直接连对方数据源的&#xff09;&#xff0c;对方提供了两台server&#xff0c;后端同事在server上面作了proxy搭建了桥接的应用&#xff08;两台server没有公网ip&#xff0c;通过…

Eclipse 教程Ⅹ

本次内容会涉及到Eclipse 重构菜单、Eclipse 添加书签和Eclipse 任务管理&#xff0c;老规矩&#xff0c;直接开始吧&#xff01; Eclipse 重构菜单 使用Eclipse重构 在项目开发中我们经常需要修改类名&#xff0c;但如果其他类依赖该类时&#xff0c;我们就需要花很多时间去…

机器学习模型的生命周期

动动发财的小手&#xff0c;点个赞吧&#xff01; 您的模型如何变化&#xff1f;Source[1] 诞生 当我们构建、训练、拟合或估计我们的模型时&#xff0c;这些数字工具就诞生了。这个阶段几乎从拥有分析目标、数据、计算机、算法以及数据科学家现在已经非常了解的其他一切开始。…

Linux [权限]

Linux 权限 Linux用户分类切换成root方法例子 切换成普通用户方法例子 短暂提权 什么是权限理论知识展示区域 修改权限(1)修改文件属性1. 采用 w/r/x的形式2. 采用八进制的形式 (2)修改身份1. 修改拥有者2. 修改所属组3. 修改拥有者 && 所属组 问题区问题1问题2问题3 L…

实在智能携手各高校打造高端数字化技能教育平台

百年大计&#xff0c;教育为本。2021年在《教育部办公厅关于印发高等职业教育专科英语、信息技术课程标准&#xff09;的通知中把机器人流程自动化列入专科信息技术课程学习计划之中&#xff0c;进一步明确职业教育中数字化人才发展方向。 一、为什么要大力培养数字化人才&…

毕业5年的同学突然告诉我,他已经是年薪30W的自动化测试工程师,我愣住了...

作为一名程序员&#xff0c;都会对自己未来的职业发展而焦虑。一方面是因为IT作为知识密集型的行业&#xff0c;知识体系复杂且知识更新速度非常快&#xff0c;“一日不学就会落后”。 另外一方面&#xff0c;IT又是劳动密集型的行业&#xff0c;不仅业人员多&#xff0c;而且个…

随机梯度下降法

梯度下降法有两个比较大的缺点&#xff1a; --计算花时间 --容易陷入局部最优解 比如以下形状的函数&#xff0c;最优解取决于初始值的选取。 梯度下降法的表达式如下&#xff0c;这个表达式使用了所有训练数据的误差&#xff1a; 随机梯度下降法表达式&#xff1a; 在随机梯…

Cmake学习记录(九)--使用Cmake交叉编译.so库

文章目录 一、前言二、相关代码三、参考链接 一、前言 目前Android编译.so的话使用Android Studio比较简单&#xff0c;但是有时候时候Android Studio的话还需要创建一个Android的项目&#xff0c;这里记录下脱离Android Studio单纯使用Cmake和C开发工具Clion(或者其他的开发工…

Prometheus+grafana+node_exporter环境搭建

原理&#xff1a; node_exporter采集数据&#xff0c;Prometheus通过配置文件Prometheus.yml配置node_exporter信息获取采集到的数据并做展示&#xff0c;grafana将Prometheus作为数据源展示node_exporter采集到的数据 拓扑图 问题&#xff1a; 1&#xff09;为什么不直接用…

万众瞩目的Nautilus Chain即将上线主网,生态正式起航

Zebec Protocol 是以流支付为定位 Web3 生态&#xff0c;该生态旨在构建一个全新的支付方式&#xff0c;以进一步丰富加密支付场景&#xff0c;并推动加密支付的大规模采用&#xff0c;该生态此前在 Solans 生态中曾取得了十分亮眼的成绩。目前&#xff0c;Zebec Protocol 正在…

Unity MVC实现背包系统(2)

在上一篇中&#xff0c;我们写了背包系统的伪代码&#xff0c;也说了mvc的设计思路&#xff0c;那么这一篇的任务就是将伪代码补全。 首先制作一个背包面板&#xff0c;我这里比较简单&#xff0c;就是一个滚动视图&#xff0c;还有一个提示文本&#xff0c;外加两个按钮&…