实际开发过程中经常遇到要实现图片验证码来防止外部使用脚本刷接口,所以说图片验证码是很有必要的一个小功能。
下面这个之前发布的,现在发现生成的图片验证码是可以被自动化工具进行识别的,具有一定的安全性问题。
1.0版本验证码:
【Java】实现图片验证码【详细代码】_图片验证码发送axios怎么弄-CSDN博客
所以在2.0版本使用不同的方式实现图片验证码。
2.0版本验证码:
前端login.tsx:只提供了关键的代码。
const LoginPage = () => {
const [image, setImage] = useState("");
const [captchaVal, setCaptchaVal] = useState("");
const [captchaKey, setCaptchaKey] = useState("");
const [captchaLoading, setCaptchaLoading] = useState(true);
const fetchImageCaptcha = () => {
setCaptchaVal("");
setCaptchaLoading(true);
system.getImageCaptcha().then((res: any) => {
setImage(res.data.image);
setCaptchaKey(res.data.key);
setCaptchaLoading(false);
});
};
useEffect(() => {
fetchImageCaptcha();
}, []);
return (
{
setEmail(e.target.value);
}}
style={{ width: 400, height: 54 }}
placeholder="请输入管理员邮箱账号"
onKeyUp={(e) => keyUp(e)}
allowClear
/>
{
setPassword(e.target.value);
}}
allowClear
style={{ width: 400, height: 54 }}
placeholder="请输入密码"
/>
{
setCaptchaVal(e.target.value);
}}
allowClear
onKeyUp={(e) => keyUp(e)}
/>
{captchaLoading && (
)}
{!captchaLoading && (
)}
);
};
前端接口:
export function getImageCaptcha() {
return client.get("/backend/system/image-captcha", {});
}
引入依赖:
com.github.penggle
kaptcha
2.3.2
后端Controller:
@RestController
@RequestMapping("/backend/system")
@Slf4j
public class SystemController {
@Autowired private ImageCaptchaService imageCaptchaService;
//验证码
@GetMapping("/image-captcha")
public JsonResponse imageCaptcha() throws IOException {
ImageCaptchaResult imageCaptchaResult = imageCaptchaService.generate();
HashMap data = new HashMap<>();
data.put("key", imageCaptchaResult.getKey());
data.put("image", imageCaptchaResult.getImage());
return JsonResponse.data(data);
}
}
Service:
public interface ImageCaptchaService {
ImageCaptchaResult generate() throws IOException;
boolean verify(String key, String code);
}
ImageCaptchaResult实体类:
@Data
public class ImageCaptchaResult {
//图形验证码的key
public String key;
public String image;
public String code;
}
ImageCaptchaServiceImpl:
@Slf4j
@Service
public class ImageCaptchaServiceImpl implements ImageCaptchaService {
@Value("${edu.captcha.cache-prefix}")
private String ConfigCachePrefix;
@Value("${edu.captcha.expire}")
private Long ConfigExpire;
@Resource
private DefaultKaptcha defaultKaptcha;
@Override
public ImageCaptchaResult generate() throws IOException {
ImageCaptchaResult imageCaptcha = new ImageCaptchaResult();
BufferedImage image;
// 图形验证码的key[api是无状态的需要key来锁定验证码的值]
String randomKey = HelperUtil.randomString(16);
imageCaptcha.setKey(randomKey);
// 生成验证码
imageCaptcha.setCode(defaultKaptcha.createText());
image = defaultKaptcha.createImage(imageCaptcha.getCode());
// 写入到redis中
RedisUtil.set(getCacheKey(randomKey), imageCaptcha.getCode(), ConfigExpire);
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
ImageIO.write(image, "png", os);
String base64 = "data:image/png;base64," + Base64Util.encode(os.toByteArray());
imageCaptcha.setImage(base64);
return imageCaptcha;
}
}
application.ymal: @Value注解需要读取到的
edu:
# 图形验证码
captcha:
expire: 300 #有效期[单位:秒,默认5分钟]
cache-prefix: "captcha:key:" #存储key的前缀
RedisUtil:
import jakarta.annotation.Resource;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
private static RedisTemplate redisTemplate;
private static final String redisPrefix = "EDU";
/**
* 注入Redis
*
* @param redisTemplate Redis对象
* @author fzr
*/
@Resource
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisUtil.redisTemplate = redisTemplate;
}
/**
* 对象句柄
*
* @return RedisTemplate
* @author fzr
*/
public static RedisTemplate handler() {
return redisTemplate;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param second 时间(秒)
* @author fzr
*/
public static void expire(String key, Long second) {
key = redisPrefix + key;
redisTemplate.expire(key, second, TimeUnit.SECONDS);
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param millisecond 时间(毫秒)
* @author fzr
*/
public static void pExpire(String key, Long millisecond) {
key = redisPrefix + key;
redisTemplate.expire(key, millisecond, TimeUnit.MILLISECONDS);
}
/**
* 指定缓存永久有效
*
* @param key 键
* @author fzr
*/
public static void persist(String key) {
key = redisPrefix + key;
redisTemplate.persist(key);
}
/**
* 根据key获取过期时间
*
* @param key 键不能为null
* @return 返回0代表为永久有效(秒)
* @author fzr
*/
public static Long ttl(String key) {
key = redisPrefix + key;
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 根据key获取过期时间
*
* @param key 键不能为null
* @return 返回0代表为永久有效(毫秒)
* @author fzr
*/
public static Long pTtl(String key) {
key = redisPrefix + key;
return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true=存在,false=不存在
* @author fzr
*/
public static Boolean exists(String key) {
key = redisPrefix + key;
return redisTemplate.hasKey(key);
}
/**
* 删除1个或多个键
*
* @param key 键(一个或多个)
* @author fzr
*/
@SuppressWarnings("unchecked")
public static void del(String... key) {
if (key.length == 1) {
key[0] = redisPrefix + key[0];
redisTemplate.delete(key[0]);
} else {
for (int i = 0; key.length > i; i++) {
key[i] = redisPrefix + key[i];
}
redisTemplate.delete((Collection) CollectionUtils.arrayToList(key));
}
}
/**
* 给key赋值一个新的key名
*
* @param oldKey 旧的key
* @param newKey 新的key
* @author fzr
*/
public static void rename(String oldKey, String newKey) {
oldKey = redisPrefix + oldKey;
newKey = redisPrefix + newKey;
redisTemplate.rename(oldKey, newKey);
}
/**
* 将当前数据库的key移动到给定的数据库db当中
*
* @param key 键
* @param db 库
* @return Boolean
* @author fzr
*/
public static Boolean move(String key, int db) {
key = redisPrefix + key;
return redisTemplate.move(key, db);
}
/**
* 获取匹配的key值
*
* @param pattern 通配符(*, ?, [])
* @return Set
* @author fzr
* @author fzr
*/
public static Set keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 随机返回一个key
*
* @return String
* @author fzr
* @author fzr
*/
public static String randomKey() {
return redisTemplate.randomKey();
}
/* ***************** common end *************** */
/**
* 按匹配获取或有KEY
*
* @param pattern 规则
* @return Set
* @author fzr
*/
public static Set matchSet(String pattern) {
Set keys = new LinkedHashSet<>();
RedisUtil.handler()
.execute(
(RedisConnection connection) -> {
try (Cursor cursor =
connection.scan(
ScanOptions.scanOptions()
.count(Long.MAX_VALUE)
.match(pattern)
.build())) {
cursor.forEachRemaining(
item -> {
keys.add(RedisSerializer.string().deserialize(item));
});
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return keys;
}
/**
* 获取key的值
*
* @param key 键
* @return Object
* @author fzr
*/
public static Object get(String key) {
key = redisPrefix + key;
return redisTemplate.opsForValue().get(key);
}
/**
* 获取旧值并设置新值
*
* @param key 键
* @param newVal 新值
* @return Object
* @author fzr
*/
public static Object getSet(String key, Object newVal) {
key = redisPrefix + key;
return redisTemplate.opsForValue().getAndSet(key, newVal);
}
/**
* 设置键值对
*
* @param key 键
* @param value 值
* @author fzr
*/
public static void set(String key, Object value) {
key = redisPrefix + key;
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置键值对并设置时间
*
* @param key 键
* @param value 值
* @param time time要大于0 如果time小于等于0 将设置无限期
* @author fzr
*/
public static void set(String key, Object value, long time) {
key = redisPrefix + key;
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return Long
* @author fzr
*/
public static Long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
key = redisPrefix + key;
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return Long
* @author fzr
*/
public static Long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
key = redisPrefix + key;
return redisTemplate.opsForValue().increment(key, -delta);
}
/* ***************** String end *************** */
/**
* 获取key中field域的值
*
* @param key 键 不能为null
* @param field 项 不能为null
* @return 值
* @author fzr
*/
public static Object hGet(String key, String field) {
key = redisPrefix + key;
return redisTemplate.opsForHash().get(key, field);
}
/**
* 判断key中有没有field域名
*
* @param key 键
* @param field 字段
* @return Boolean
* @author fzr
*/
public static Boolean hExists(String key, Object field) {
key = redisPrefix + key;
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
* @author fzr
*/
public Map
KaptchaConfig:用于配置 Kaptcha 验证码生成器的属性,给DefaultKaptcha进行配置属性,可以说是给图片验证码增加属性。
/**
* 图片验证码的配置类
* */
@Configuration
public class KaptchaConfig {
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否边框
properties.setProperty(KAPTCHA_BORDER, "no");
// 字符颜色
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "red");
// 干扰线颜色
properties.setProperty(KAPTCHA_NOISE_COLOR, "red");
// 字符间距
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "5");
// 图片宽度
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "150");
// 图片高度
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "50");
// 字符大小
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "40");
// 字符长度
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 字体样式
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
/// 图片样式 水纹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.FishEyeGimpy");
defaultKaptcha.setConfig(new Config(properties));
return defaultKaptcha;
}
}
如果你想要设置图片验证码的相关属性可以对properties进行对应的设置和修改即可。