SpringBoot--图片验证码kaptcha

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

前言

参考链接:

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值,和客户端传入的验证码进行匹配。
注意:

  • 缓存的时间

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/baidu_33256174/article/details/130332239