基于Kaptcha + Redis实现图片验证码
2023-08-09 14:53:19 # Tools # Kaptcha

0. 简介

Kaptcha是谷歌开源的简单实用的验证码生成工具。通过设置参数,可以自定义验证码大小、颜色、显示的字符等等。

这篇博客基于SpringBoot + Kaptcha + Redis实现一个简单的图片验证码功能

1. 添加Maven依赖

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

<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>

2. 添加配置类

2.1 Kaptcha配置

注册 DefaultKaptcha 到 IOC 容器中,在 config 包中,新建 KaptchaConfig 类:

@Configuration
public class KaptchaConfig {

@Bean
public DefaultKaptcha getDefaultKaptcha(){
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 图片边框
properties.put(Constants.KAPTCHA_BORDER, "no");
// 验证码长度
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 取值
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
// 干扰线
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

可供配置的属性

KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
KAPTCHA_SESSION_DATE = "KAPTCHA_SESSION_DATE";
KAPTCHA_SESSION_CONFIG_KEY = "kaptcha.session.key";
KAPTCHA_SESSION_CONFIG_DATE = "kaptcha.session.date";
KAPTCHA_BORDER = "kaptcha.border"; //边框
KAPTCHA_BORDER_COLOR = "kaptcha.border.color"; //边框颜色
KAPTCHA_BORDER_THICKNESS = "kaptcha.border.thickness"; //边框厚度
KAPTCHA_NOISE_COLOR = "kaptcha.noise.color"; //干扰线颜色
KAPTCHA_NOISE_IMPL = "kaptcha.noise.impl"; //干扰线实现
KAPTCHA_OBSCURIFICATOR_IMPL = "kaptcha.obscurificator.impl"; //图片样式
KAPTCHA_PRODUCER_IMPL = "kaptcha.producer.impl"; //图片实现类
KAPTCHA_TEXTPRODUCER_IMPL = "kaptcha.textproducer.impl"; //文本实现类
KAPTCHA_TEXTPRODUCER_CHAR_STRING = "kaptcha.textproducer.char.string"; //文本集合,验证码值从此集合中获取
KAPTCHA_TEXTPRODUCER_CHAR_LENGTH = "kaptcha.textproducer.char.length"; //验证码长度
KAPTCHA_TEXTPRODUCER_FONT_NAMES = "kaptcha.textproducer.font.names"; //字体
KAPTCHA_TEXTPRODUCER_FONT_COLOR = "kaptcha.textproducer.font.color"; //字体颜色
KAPTCHA_TEXTPRODUCER_FONT_SIZE = "kaptcha.textproducer.font.size"; //字体大小
KAPTCHA_TEXTPRODUCER_CHAR_SPACE = "kaptcha.textproducer.char.space"; //文字间隔
KAPTCHA_WORDRENDERER_IMPL = "kaptcha.word.impl"; //文字渲染器
KAPTCHA_BACKGROUND_IMPL = "kaptcha.background.impl"; //背景实现类
KAPTCHA_BACKGROUND_CLR_FROM = "kaptcha.background.clear.from"; //背景颜色渐变,开始颜色
KAPTCHA_BACKGROUND_CLR_TO = "kaptcha.background.clear.to"; //背景颜色渐变,结束颜色
KAPTCHA_IMAGE_WIDTH = "kaptcha.image.width"; //图片宽
KAPTCHA_IMAGE_HEIGHT = "kaptcha.image.height"; //图片高

2.2 Redis配置

application.yml中的配置

spring:
redis:
host: ***.***.***.***
port: 6379
password: ******
database: 0
# 连接超时时间
timeout: 180000
lettuce:
pool:
# 连接池最大连接数(负值表示没有限制)
max-active: 20
# 最大阻塞等待时间(负数表示没有限制)
max-wait: -1
# 连接池中的最小空闲连接
max-idle: 5
# 连接池中的最小空闲连接
min-idle: 0

注册 RedisTemplate 到 IOC 容器中,在 config 包中,新建 RedisConfig 类:

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 1.创建 redisTemplate 模版
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 2.关联 redisConnectionFactory
template.setConnectionFactory(factory);
// 3.创建 序列化类
GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
// 6.序列化类,对象映射设置
// 7.设置 value 的转化格式和 key 的转化格式
template.setValueSerializer(genericToStringSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}

3. 接口开发

3.1 Controller层

@Autowired
KaptchaService kaptchaService;

/**
* 发送图形验证码
*/
@GetMapping("/captcha/send")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
String deviceCode = request.getHeader("deviceCode");
BufferedImage image = kaptchaService.getCaptcha(deviceCode);
ServletOutputStream outputStream;
try {
outputStream = response.getOutputStream();
ImageIO.write(image, "jpg", outputStream);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 验证图形验证码
*/
@GetMapping("/captcha/check")
public void checkCaptcha(@RequestParam("captcha") String captcha, HttpServletRequest request) {
String deviceCode = request.getHeader("deviceCode");
boolean result = kaptchaService.checkCaptcha(captcha, deviceCode);
}

3.2 Service层

设置有效期为30分钟,且30min内最多尝试20次,根据设备id作为唯一标识

@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
Producer producer;

@Override
public BufferedImage getCaptcha(String deviceCode) {
String captchaText = producer.createText();
String key = deviceCode + ":captcha";
String countKey = "count:" + deviceCode + ":captcha";
// 限制次数
String count = (String) redisTemplate.opsForValue().get(countKey);
if (count == null) {
redisTemplate.opsForValue().set(countKey, "1", 30, TimeUnit.MINUTES);
} else if (Integer.parseInt(count) <= 20) {
redisTemplate.opsForValue().increment(countKey);
} else if (Integer.parseInt(count) > 20) {
throw new RuntimeException("请稍后尝试");
}
// 设置值
redisTemplate.opsForValue().set(key, captchaText, 30, TimeUnit.MINUTES);

return producer.createImage(captchaText);
}

@Override
public boolean checkCaptcha(String captcha, String deviceCode) {
String key = "captcha" + deviceCode + ":code";
String value = (String) redisTemplate.opsForValue().get(key);
if (captcha.equals(value)) {
redisTemplate.delete(key);
return true;
} else {
return false;
}
}

4. 测试

使用Apifox进行测试

image-20220811101923033