SpringBoot--图片验证码kaptcha

news2024/12/30 2:15:00

本文介绍如何在SpringBoot中整合kaptcha,以及如何配置kaptcha,生成验证码和校验等

文章目录

  • 前言
  • 环境搭建
    • 项目结构
    • 添加依赖
  • 代码实现
    • KaptchaConfig
    • Knife4jConfig
    • domain
    • Service
    • util
    • controller
  • 测试
    • 生成验证码
    • 校验验证码

前言

参考链接:

  • Github链接
  • 链接1

Kaptcha 是一个Google开源,可自由配置的图片验证码生成工具,功能十分强大。使用Kaptcha时可以配置图片的宽高、字符内容、干扰类型等,自定义样式。

环境搭建

完整项目,参考https://github.com/miaoyang/spring-learn.git ,该项目还包括其他一些中间件的使用,比如Redis,MongoDB,Swagger等。

项目结构

在这里插入图片描述
将验证码单独写成了一个微服务,在实际开发中,单体项目可以裁剪部分。

添加依赖

pom文件中引入必要的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-learn</artifactId>
        <groupId>org.ym</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>learn-checkcode</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--check code-->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.ym</groupId>
            <artifactId>learn-common-core</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.ym</groupId>
            <artifactId>learn-common-swagger</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.ym</groupId>
            <artifactId>learn-common-redis</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

</project>

application.yml配置

server:
  port: 9205
spring:
  application:
    name: learn-checkcode
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  redis:
    host: localhost # Redis服务器地址
    database: 0 # Redis数据库索引(默认为0)
    port: 6379 # Redis服务器连接端口
    password:  # Redis服务器连接密码(默认为空)
    timeout: 3000ms # 连接超时时间(毫秒)

checkcode:
  length: 4
  prefix-key: check_code_
  expire-time: 300 # 300s

代码实现

KaptchaConfig

CheckCodeProperties配置了验证码的一些基本属性,在application.yml 可以修改。

package com.ym.learn.checkcode.config;

import lombok.Data;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 19:44
 * @Desc:
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "checkcode")
public class CheckCodeProperties {
    /**
     * 验证码长度
     */
    private Integer length;
    /**
     * key前缀
     */
    private String prefixKey;
    /**
     * 缓存过期时间
     */
    private Integer expireTime;

}

package com.ym.learn.checkcode.config;

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;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 16:25
 * @Desc: 验证码配置
 */
@Configuration
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha defaultKaptcha() {
        Properties properties = new Properties();
        properties.put("kaptcha.border", "no");

        // 文本宽度和长度
        properties.put("kaptcha.textproducer.char.space", "10");
        properties.put("kaptcha.textproducer.char.length","4");
        // 宽度和高度
        properties.put("kaptcha.image.height","34");
        properties.put("kaptcha.image.width","130");
        // 字体大小和颜色
        properties.put("kaptcha.textproducer.font.size","25");
        properties.put("kaptcha.textproducer.font.color", "black");
        // 背景
        properties.setProperty("kaptcha.background.clear.from", "white");
        properties.setProperty("kaptcha.background.clear.to", "white");

        properties.put("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");

        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

Knife4jConfig

接口文档配置,该部分参考SpringBoot整合Knife4j,在本文章中,可有可无,不过为了方便测试接口。

package com.ym.learn.checkcode.config;

import com.ym.learn.swagger.config.BaseKnife4jConfig;
import com.ym.learn.swagger.domain.Knife4jProperties;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.oas.annotations.EnableOpenApi;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 22:07
 * @Desc:
 */
@Configuration
@EnableOpenApi
public class Knife4jConfig extends BaseKnife4jConfig {
    @Override
    public Knife4jProperties knife4jProperties() {
        return Knife4jProperties.builder()
                .apiBasePackage("com.ym.learn.checkcode")
                .title("验证码服务接口文档")
                .description("验证码服务接口文档")
                .contactName("ym")
                .version("1.0")
                .enableSecurity(false)
                .build();
    }
}

domain

package com.ym.learn.checkcode.domain;

import lombok.Builder;
import lombok.Data;
import lombok.ToString;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 17:36
 * @Desc: 返回给客户端的Code
 */
@Data
@ToString
@Builder
public class CodeVo {
    /**
     * 用于验证的key
     */
    private String key;
    /**
     * 校验码
     */
    private String aliasing;
}

Service

package com.ym.learn.checkcode.service;

import com.ym.learn.checkcode.domain.CodeVo;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 17:32
 * @Desc:
 */
public interface CheckCodeService {

    /**
     * 校验验证码
     * @param key
     * @param code
     * @return
     */
    boolean verifyCode(String key, String code);

    /**
     * 生成验证码
     * @param length
     * @return
     */
    CodeVo generateCode(Integer length, String prefixKey,Integer expireTime);
}

package com.ym.learn.checkcode.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import com.ym.learn.checkcode.domain.CodeVo;
import com.ym.learn.checkcode.service.CheckCodeService;
import com.ym.learn.checkcode.util.CodeUtil;
import com.ym.learn.core.utils.SignUtil;
import com.ym.learn.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 17:33
 * @Desc:
 */
@Service
@Slf4j
public class CheckCodeServiceImpl implements CheckCodeService {
    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @Autowired
    private RedisService redisService;

    @Override
    public boolean verifyCode(String key, String code) {
        if (StrUtil.isEmpty(key) || StrUtil.isEmpty(code)){
            return false;
        }
        String localCode = (String)redisService.get(key);
        if (StrUtil.isEmpty(localCode)){
            return false;
        }
        if (!code.equalsIgnoreCase(localCode)){
            return false;
        }
        // 删除缓存
        redisService.del(key);
        return true;
    }

    @Override
    public CodeVo generateCode(Integer length, String prefixKey, Integer expireTime) {
        String code = CodeUtil.generateCode(length);
        String key = CodeUtil.generateKey(prefixKey);
        // 缓存redis
        redisService.set(key,code,expireTime);
        // 获取base64编码后的img
        String codeImg = generateCodeImg(code);
        return CodeVo.builder()
                .key(key)
                .aliasing(codeImg)
                .build();
    }

    /**
     * 生成codeImage
     * @param code
     * @return
     */
    private String generateCodeImg(String code){
        BufferedImage bufferedImage = defaultKaptcha.createImage(code);
        ByteOutputStream byteOutputStream = null;
        String codeImg = "";
        try {
            byteOutputStream = new ByteOutputStream();
            ImageIO.write(bufferedImage,"png",byteOutputStream);
            codeImg = "data:image/png;base64,"+SignUtil.encodeBase64(byteOutputStream.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (byteOutputStream != null) {
                byteOutputStream.close();
            }
        }
        return codeImg;
    }
}

util

用于生成Key和Code

package com.ym.learn.checkcode.util;

import java.util.Random;
import java.util.UUID;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 17:05
 * @Desc: 验证码工具类
 */
public class CodeUtil {
    /**
     * Code字符的取值范围
     */
    public static final String CODE_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    /**
     * 生成UUID,包括前缀
     * @param prefix
     * @return
     */
    public static String generateKey(String prefix){
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        return prefix+uuid;
    }

    /**
     * 生成指定长度的code
     * @param len 不指定时,默认为4
     * @return
     */
    public static String generateCode(Integer len){
        if (len <= 0){
            len = 4;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++) {
            Random random = new Random();
            int nextInt = random.nextInt(CODE_NUM.length());
            sb.append(CODE_NUM.charAt(nextInt));
        }
        return sb.toString();
    }

}

controller

package com.ym.learn.checkcode.controller;

import com.ym.learn.checkcode.config.CheckCodeProperties;
import com.ym.learn.checkcode.domain.CodeVo;
import com.ym.learn.checkcode.service.CheckCodeService;
import com.ym.learn.core.api.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @Author: Yangmiao
 * @Date: 2023/4/21 17:29
 * @Desc: 验证码接口
 */
@Api(tags = "验证码接口")
@RestController
@RequestMapping("/checkcode")
public class CheckCodeController {

    @Autowired
    private CheckCodeService checkCodeService;

    @Autowired
    private CheckCodeProperties codeProperties;

    @ApiOperation(value = "获取验证码")
    @GetMapping("/getCheckCode")
    public R getCheckCode(){
        CodeVo codeVo = checkCodeService.generateCode(codeProperties.getLength(), codeProperties.getPrefixKey(), codeProperties.getExpireTime());
        return R.ok(codeVo);
    }

    @ApiOperation(value = "校验验证码")
    @PostMapping("/verifyCheckCode")
    public R verifyCheckCode(@ApiParam(name = "key")@RequestParam("key")String key,
                             @ApiParam(name = "code")@RequestParam("code")String code){
        boolean ret = checkCodeService.verifyCode(key, code);
        return R.ok(ret);
    }

}

测试

使用Knfie4j测试接口
在这里插入图片描述

生成验证码

在这里插入图片描述
aliasing 对应的value复制到浏览器地址,解析出图片如下:
在这里插入图片描述

校验验证码

客户端需要传入key 和验证码数字,服务端从缓存中取出key对应的value值,和客户端传入的验证码进行匹配。
注意:

  • 缓存的时间

在这里插入图片描述

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

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

相关文章

设计模式:结构型模式 - 适配器模式

文章目录 1.概述2.结构3.类适配器模式4.对象适配器模式5.应用场景6.JDK源码解析 - Reader 与 InputStream 1.概述 如果去欧洲国家去旅游的话&#xff0c;他们的插座如下图最左边&#xff0c;是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑&#xff0c;手机…

【Android车载系列】第9章 车载通信-Socket实现IPC通信机制(实现仿FdBus效果)

1 FDBus简介 FDBus 基于 Socket (TCP 和 Unix domain) 之上的IPC机制, 采用 Google protobuf 做序列化和反序列化。 FDBus还支持字符串形式的名字作为server地址。通过 name server 自动为 server 分配Unix domain 地址和 TCP 端口号, 实现 client 和server 之间用服务名字寻址…

【C++】哈希的应用:位图和布隆过滤器

目录 1. 位图1.1 位图的概念1.2 位图的结构1.3 位图的实现 2. 布隆过滤器2.1 概念2.2 结构2.3 布隆过滤器的实现 1. 位图 1.1 位图的概念 &#x1f4ad;位图&#xff08;bitset&#xff09;是一种基于哈希思想设计的数据结构&#xff0c;其功能主要用于判断数据是否已存在。适…

【设计模式】模板方法模式--让你的代码更具灵活性与可扩展性

文章目录 前言模板方法模式的定义核心组成模板方法模式与其他设计模式的区别 代码实现抽象类具体类Client 经典类图spring中的例子 总结 前言 在软件开发中&#xff0c;设计模式是一种经过实践检验的、可复用的解决方案&#xff0c;它们可以帮助我们解决某一特定领域的典型问题…

【2023 · CANN训练营第一季】应用开发深入讲解——第一章 模型转换(1)

学习目标 学习资源 1.模型转换 模型转换基础 在线课程 文档AIPP功能说明 文档 应用开发&#xff08;初级&#xff09; 课程目标&#xff1a; 了解AscendCL的使用场景、基本概念、基本开发流程。 学会使用AscendCL接口开发基础推理应用&#xff0c;应用中包含对图片的基本处理…

javaScript常见面试题(二)

一、浅拷贝和深拷贝 浅拷贝&#xff1a;&#xff08;1&#xff09;对于基本数据类型&#xff0c;拷贝的是值&#xff0c;修改的时候数据互不影响&#xff1b;&#xff08;2&#xff09;对于引用数据类型&#xff0c;拷贝的是在堆内存中存储的内存地址&#xff0c;修改的时候&a…

mapreduce基础: 手写本地wordcount案例

文章目录 一、源代码1. WordCountMapper类2. WordCountReducer类3. WordCountDriver类 二、运行截图 一、源代码 1. WordCountMapper类 package org.example.wordcount;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apac…

yum库和nfs共享服务

yum库 redhat&#xff0c;centos用的是.rpm的包&#xff0c;用yum解决依赖包关系下载 ubuntu&#xff0c;debian用的是.deb的包&#xff0c;用apt解决依赖包的关系下载 yum软件仓库的提供方式 FTP服务: ftp://… HTTP服务: http://…或者https&#xff1a;//… 本地目录: fi…

母婴品牌内容输出怎么做?“四板斧”送你

新媒体时代&#xff0c;信息大爆炸&#xff0c;人们的注意力有限&#xff0c;有噱头和亮点的内容才能博得注意&#xff0c;成为用户关注的焦点。 母婴行业重视品牌效益和产品的质量&#xff0c;毕竟类似“三聚氰胺”的惨剧谁也不希望再发生。母婴产品的质量依赖技术和生产线支…

C语言从入门到精通第9天(循环结构的使用)

循环结构的使用 while语句do-while语句for语句嵌套循环 循环结构可以重复的执行一段代码块&#xff0c;在C语言中提供了三种不同类型的循环结构&#xff1a;for、while和do-while。 while语句 语法&#xff1a; while(表达式){ 语句&#xff1b; } 如果表达式为真则执行结构体…

并发编程学习笔记

为什么学&#xff1f; 只要去做一个技术含量稍微好点的系统&#xff0c;并发包下面的东西是很容易会要用到的 synchronized(Object){ } 主要是用来给对象加锁 synchronized底层原理 首先监视器的计数器是0&#xff0c;然后线程一访问&#xff0c;会将值改为1&#xff0c;…

[Gitops--4] OpenELB

OpenELB OpenELB是一个开源的负载均衡器,功能和metalLB类似 OpenELB主要两种工作模式: Layer2和BGP模式.目前OpenELB的BGP不支持ipv6 OpenELB核心思想就是通过某种方式将特定的VIP的流量引导k8s集群中,然后通过Kube-proxy将流量转发到后面的特定服务. 1. OpenELB介绍 1.1 La…

对于python文件,敲下回车后发生了什么

引言 我们常说 Python 一是门解释型语言&#xff0c;只需要敲下 python code.py就可以运行编写的代码&#xff0c;而无需使用类似于 javac 或者 gcc 进行编译。那么&#xff0c;Python 解释器是真的一行一行读取 Python 源代码而后执行吗? 实际上&#xff0c;Python 在执行程序…

为什么要进行倾斜摄影三维模型的顶层合并?

为什么要进行倾斜摄影三维模型的顶层合并&#xff1f; 1、倾斜摄影三维模型顶层合并的重要性 倾斜摄影三维模型的顶层合并是指将拍摄同一区域的多个倾斜角度的影像进行融合&#xff0c;生成一个连续的、完整的三维地理信息数据。其原因主要有以下几点&#xff1a; &#xff0…

关于倾斜摄影三维模型轻量化数据大小和质量关系分析

关于倾斜摄影三维模型轻量化数据大小和质量关系分析 倾斜摄影三维模型轻量化是一种常用的技术&#xff0c;通过对原始三维模型数据进行压缩和简化&#xff0c;减小其数据大小&#xff0c;从而提高数据传输和展示效率。然而&#xff0c;轻量化过程中可能会对数据质量产生影响。以…

性能测试——安装Loadrunner11.0的详细步骤

一、下载Loadrunner11.0版本 去相关网站下载即可 二、安装 &#xff08;windows与虚拟机上安装操作大相径庭&#xff09; 1、将ISO文件导入&#xff0c;打开光驱&#xff0c;运行“setup.exe“ 2、点击安装&#xff0c;部分机器会提示缺少“Microsoft Visual C 2005 SP1运行组…

有效的字母异位词

给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetco…

Java 将json中key值中带有下划线的部分转为驼峰格式

一、背景说明 在开发过程中&#xff0c;有时会遇到第三方厂商提供的接口返回结果不是严格按照驼峰命名&#xff0c;需要将其中带有下划线的字段进行格式化转换为驼峰命名。 如下图中的self_auth、user_id、user_name、creator_name 和 others_auths 等 key 值。 如果是对 JS…

Mysql 45讲和45问笔记(未完待续0203/04/24)

一、mysql 45讲 1&#xff09;索引的本质讲解 定义解释 所以是帮助Mysql高效获取数据的排好序的数据结构 索引数据结构 ①二叉树 ②红黑树 ③Hash表 ④B-Tree 原理讲解 可以看到右边的数据结构里面&#xff0c;是按照k-v来存数据结构的&#xff0c;key是col2的字段&#xf…

【Linux】线程-线程概念

线程概念 什么是线程线程的优点和缺点线程的用途和线程异常线程与进程的区别 什么是线程 实际上&#xff0c;线程是一个进程内部的控制序列&#xff0c;一个程序的一个执行线路就是一个线程。 并且一个进程中至少有一个线程&#xff0c;本质上&#xff0c;一个进程内部如果有多…