目录
原理
导入验证码依赖
Redis工具类RedisUtils
配置类CaptchaConfig
验证码的文本生成器
在SpringBoot里面配置RedisTemplate
后端返回验证码接口
登录验证(在登录方法之前执行)
Login.vue
通过工具类生成一条算术的验证规则,类似于这样的:1+1=2,其中1+1就是算术规则,2是算术结果。
算术规则我们会通过图片流的形式返回给前端显示出来,让用户看到这个算术规则,计算出结果code。
算术结果我们会存储到redis缓存里面,并设置唯一的一个uuid key,这样我们就可以在用户提交登录表单的时候获取到这个key,也就是uuid。再从redis里面拿到之前缓存的算术结果,再跟用户提交的算术结果code做比较,如果我们生成的算术结果跟用户提交的算术结果code是一致的,那么登录验证通过,否则不通过。
com.github.penggle
kaptcha
2.3.2
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnectionCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = {"unchecked"})
@Component
@Slf4j
public class RedisUtils {
private static RedisTemplate staticRedisTemplate;
private final RedisTemplate redisTemplate;
public RedisUtils(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
// Springboot启动成功之后会调用这个方法
@PostConstruct
public void initRedis() {
// 初始化设置 静态staticRedisTemplate对象,方便后续操作数据
staticRedisTemplate = redisTemplate;
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public static void setCacheObject(final String key, final T value) {
staticRedisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public static void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) {
staticRedisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public static T getCacheObject(final String key) {
return (T) staticRedisTemplate.opsForValue().get(key);
}
/**
* 删除单个对象
*
* @param key 缓存键值
*/
public static boolean deleteObject(final String key) {
return Boolean.TRUE.equals(staticRedisTemplate.delete(key));
}
/**
* 获取单个key的过期时间
*
* @param key 缓存键值
* @return 过期时间
*/
public static Long getExpireTime(final String key) {
return staticRedisTemplate.getExpire(key);
}
/**
* 发送ping命令
* redis 返回pong
*/
public static void ping() {
String res = staticRedisTemplate.execute(RedisConnectionCommands::ping);
log.info("Redis ping ==== {}", res);
}
public static Long incr(String key) {
return staticRedisTemplate.opsForValue().increment(key);
}
}
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;
import static com.google.code.kaptcha.Constants.*;
/**
* 验证码配置
*
*/
@Configuration
public class CaptchaConfig {
@Bean
public DefaultKaptcha getCaptchaBeanMath() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "200,200,200");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "40");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ttl.common.config.CaptchaTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
通过这个生成器生成算术的规则
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import java.util.Random;
/**
* 验证码文本生成器
*/
public class CaptchaTextCreator extends DefaultTextCreator {
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
@Override
public String getText() {
Integer result = 0;
Random random = new Random();
int x = random.nextInt(10);
int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder();
int randomoperands = (int) Math.round(Math.random() * 2);
if (randomoperands == 0) {
result = x * y;
suChinese.append(CNUMBERS[x]);
suChinese.append("*");
suChinese.append(CNUMBERS[y]);
} else if (randomoperands == 1) {
if (!(x == 0) && y % x == 0) {
result = y / x;
suChinese.append(CNUMBERS[y]);
suChinese.append("/");
suChinese.append(CNUMBERS[x]);
} else {
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
} else if (randomoperands == 2) {
if (x >= y) {
result = x - y;
suChinese.append(CNUMBERS[x]);
suChinese.append("-");
suChinese.append(CNUMBERS[y]);
} else {
result = y - x;
suChinese.append(CNUMBERS[y]);
suChinese.append("-");
suChinese.append(CNUMBERS[x]);
}
} else {
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
suChinese.append("=?@" + result);
return suChinese.toString();
}
}
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
// 设置 key的序列化方式 防止默认的jdk序列化方式出现二进制码 看不懂
redisTemplate.setKeySerializer(new StringRedisSerializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(objectMapper, Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
/**
* 获取图形算术验证码
*/
@GetMapping("/captcha")
public Result getCaptcha() {
// 验证码存储到redis
String uuid = IdUtil.fastSimpleUUID();
String captchaKey = Constants.REDIS_KEY_CAPTCHA + uuid;
// 1+1=2 1+1@2
String captchaText = producer.createText();
String captchaStr = captchaText.substring(0, captchaText.lastIndexOf("@"));// 1+1
String captchaCode = captchaText.substring(captchaText.lastIndexOf("@") + 1);// 2
// 将算术运算结果存储到redis
RedisUtils.setCacheObject(captchaKey, captchaCode, Constants.CAPTCHA_EXPIRE_MINUTES, TimeUnit.MINUTES);
// 返回图片的base64编码
try (FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream()){
BufferedImage image = producer.createImage(captchaStr);
ImageIO.write(image, "jpg", outputStream);
Map map = new HashMap<>();
map.put("uuid", uuid);
map.put("img", Base64.encode(outputStream.toByteArray()));
return Result.success(map);
} catch (Exception e) {
log.error("生成验证码错误", e);
return Result.error("获取验证码错误");
}
}
String uuid = user.getUuid();
String captchaKey = Constants.REDIS_KEY_CAPTCHA + uuid;
String captchaCode = RedisUtils.getCacheObject(captchaKey);
if (captchaCode == null) {
throw new CustomException("验证码已失效");
}
if (!user.getCode().equals(captchaCode)) {
throw new CustomException("验证码错误");
}
// 验证完成后删除redis缓存
RedisUtils.deleteObject(captchaKey);
欢 迎 登 录
登 录