本文介绍如何在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值,和客户端传入的验证码进行匹配。
注意:
- 缓存的时间