feat(captcha): 新增行为验证码自动配置

This commit is contained in:
Bull-BCLS
2023-12-25 21:01:38 +08:00
parent 0a0d022586
commit bcb13326c6
11 changed files with 538 additions and 3 deletions

View File

@@ -0,0 +1,31 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-captcha</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-captcha-behavior</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 验证码模块 - 行为验证码</description>
<dependencies>
<!-- AJ-Captcha行为验证码包含滑动拼图、文字点选两种方式UI支持弹出和嵌入两种方式 -->
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,143 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.captcha.behavior.autoconfigure;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.anji.captcha.model.common.Const;
import com.anji.captcha.service.CaptchaService;
import com.anji.captcha.service.impl.CaptchaServiceFactory;
import com.anji.captcha.util.ImageUtils;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 行为验证码自动配置
*
* @author Bull-BCLS
* @since 1.1.0
*/
@Slf4j
@AutoConfiguration
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "continew-starter.captcha.behavior", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(BehaviorCaptchaProperties.class)
public class BehaviorCaptchaAutoConfiguration {
private final BehaviorCaptchaProperties properties;
/**
* 自定义缓存实现配置
*/
@Configuration
@Import({BehaviorCaptchaCacheConfiguration.Redis.class, BehaviorCaptchaCacheConfiguration.Local.class, BehaviorCaptchaCacheConfiguration.Custom.class})
protected static class BehaviorCaptchaCacheAutoConfiguration {
}
/**
* 行为验证码服务接口
*/
@Bean
@ConditionalOnMissingBean
public CaptchaService captchaService() {
Properties config = new Properties();
config.put(Const.CAPTCHA_CACHETYPE, properties.getCacheType().name().toLowerCase());
config.put(Const.CAPTCHA_WATER_MARK, properties.getWaterMark());
config.put(Const.CAPTCHA_FONT_TYPE, properties.getFontType());
config.put(Const.CAPTCHA_TYPE, properties.getType().getCodeValue());
config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, properties.getInterferenceOptions());
config.put(Const.ORIGINAL_PATH_JIGSAW, StrUtil.emptyIfNull(properties.getJigsawBaseMapPath()));
config.put(Const.ORIGINAL_PATH_PIC_CLICK, StrUtil.emptyIfNull(properties.getPicClickBaseMapPath()));
config.put(Const.CAPTCHA_SLIP_OFFSET, properties.getSlipOffset());
config.put(Const.CAPTCHA_AES_STATUS, String.valueOf(properties.getEnableAES()));
config.put(Const.CAPTCHA_WATER_FONT, properties.getWaterFont());
config.put(Const.CAPTCHA_CACAHE_MAX_NUMBER, properties.getCacheNumber());
config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, properties.getTimingClear());
config.put(Const.HISTORY_DATA_CLEAR_ENABLE, properties.getHistoryDataClearEnable());
config.put(Const.REQ_FREQUENCY_LIMIT_ENABLE, properties.getReqFrequencyLimitEnable());
config.put(Const.REQ_GET_LOCK_LIMIT, properties.getReqGetLockLimit());
config.put(Const.REQ_GET_LOCK_SECONDS, properties.getReqGetLockSeconds());
config.put(Const.REQ_GET_MINUTE_LIMIT, properties.getReqGetMinuteLimit());
config.put(Const.REQ_CHECK_MINUTE_LIMIT, properties.getReqCheckMinuteLimit());
config.put(Const.REQ_VALIDATE_MINUTE_LIMIT, properties.getReqVerifyMinuteLimit());
config.put(Const.CAPTCHA_FONT_SIZE, properties.getFontSize());
config.put(Const.CAPTCHA_FONT_STYLE, properties.getFontStyle());
config.put(Const.CAPTCHA_WORD_COUNT, 4);
if (StrUtil.startWith(properties.getJigsawBaseMapPath(), "classpath:")
|| StrUtil.startWith(properties.getPicClickBaseMapPath(), "classpath:")) {
// 自定义 resources 目录下初始化底图
config.put(Const.CAPTCHA_INIT_ORIGINAL, true);
initializeBaseMap(properties.getJigsawBaseMapPath(), properties.getPicClickBaseMapPath());
}
return CaptchaServiceFactory.getInstance(config);
}
/**
* 初始化 行为/点选 验证码底图
*
* @param jigsaw 行为验证码底图路径
* @param picClick 点选验证码底图路径
*/
private static void initializeBaseMap(String jigsaw, String picClick) {
ImageUtils.cacheBootImage(getResourcesImagesFile(jigsaw + "/original/*.png"),
getResourcesImagesFile(jigsaw + "/slidingBlock/*.png"),
getResourcesImagesFile(picClick + "/*.png"));
}
/**
* 获取图片
*
* @param path 图片路径
* @return key图片文件名称value图片
*/
private static Map<String, String> getResourcesImagesFile(String path) {
Map<String, String> imgMap = new HashMap<>();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
Resource[] resources = resolver.getResources(path);
for (Resource resource : resources) {
String imageName = resource.getFilename();
byte[] imageValue = FileUtil.readBytes(resource.getFile());
imgMap.put(imageName, Base64.encode(imageValue));
}
} catch (Exception e) {
log.error("读取路径为 [{}] 下的图片文件失败", path, e);
}
return imgMap;
}
@PostConstruct
public void postConstruct() {
log.info("[ContiNew Starter] - Auto Configuration 'Behavior Captcha' completed initialization.");
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.captcha.behavior.autoconfigure;
import cn.hutool.core.util.ReflectUtil;
import com.anji.captcha.service.CaptchaCacheService;
import com.anji.captcha.service.impl.CaptchaCacheServiceMemImpl;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.client.RedisClient;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import top.charles7c.continew.starter.cache.redisson.autoconfigure.RedissonAutoConfiguration;
import top.charles7c.continew.starter.captcha.behavior.impl.BehaviorCaptchaCacheServiceImpl;
/**
* 行为验证码缓存配置
*
* @author Bull-BCLS
* @since 1.1.0
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
abstract class BehaviorCaptchaCacheConfiguration {
/**
* 自定义缓存实现类-Redis
*/
@ConditionalOnClass(RedisClient.class)
@ConditionalOnMissingBean(CaptchaCacheService.class)
@AutoConfigureBefore(RedissonAutoConfiguration.class)
@ConditionalOnProperty(name = "continew-starter.captcha.behavior.cache-type", havingValue = "redis")
static class Redis {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Behavior-CaptchaCache-Redis' completed initialization.");
}
@Bean
public CaptchaCacheService captchaCacheService() {
return new BehaviorCaptchaCacheServiceImpl();
}
}
/**
* 自定义缓存实现类-内存
*/
@ConditionalOnMissingBean(CaptchaCacheService.class)
@ConditionalOnProperty(name = "continew-starter.captcha.behavior.cache-type", havingValue = "local")
static class Local {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Behavior-CaptchaCache-Local' completed initialization.");
}
@Bean
public CaptchaCacheService captchaCacheService() {
return new CaptchaCacheServiceMemImpl();
}
}
/**
* 自定义缓存实现类-自定义
*/
@ConditionalOnMissingBean(CaptchaCacheService.class)
@ConditionalOnProperty(name = "continew-starter.captcha.behavior.cache-type", havingValue = "custom")
static class Custom {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Behavior-CaptchaCache-Custom' completed initialization.");
}
@Bean
public CaptchaCacheService captchaCacheService(BehaviorCaptchaProperties properties) {
return ReflectUtil.newInstance(properties.getCacheImpl());
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.captcha.behavior.autoconfigure;
import com.anji.captcha.model.common.CaptchaTypeEnum;
import com.anji.captcha.service.CaptchaCacheService;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.charles7c.continew.starter.captcha.behavior.enums.StorageType;
import java.awt.*;
/**
* 行为验证码配置属性
*
* @author Bull-BCLS
* @since 1.1.0
*/
@Data
@ConfigurationProperties(prefix = "continew-starter.captcha.behavior")
public class BehaviorCaptchaProperties {
/**
* 是否启用行为验证码
*/
private boolean enabled = false;
/**
* 是否开启 AES 坐标加密默认true
*/
private Boolean enableAES = true;
/**
* 验证码类型(默认:滑动验证码)
*/
private CaptchaTypeEnum type = CaptchaTypeEnum.BLOCKPUZZLE;
/**
* 缓存类型默认LOCAL 内存)
*/
private StorageType cacheType = StorageType.LOCAL;
/**
* 自定义缓存类型(当 cacheType 为 CUSTOM 时必填)
*/
private Class<? extends CaptchaCacheService> cacheImpl;
/**
* 滑动拼图底图路径(为空则使用默认底图)(路径下需要有两个文件夹,分别为 original存放底图slidingBlock存放滑块
*/
private String jigsawBaseMapPath;
/**
* 校验滑动拼图允许误差偏移量默认5像素
*/
private String slipOffset = "5";
/**
* 点选文字底图路径(为空则使用默认底图)
*/
private String picClickBaseMapPath;
/**
* 点选文字验证码的文字字体(默认:文泉驿正黑)
*/
private String fontType = "WenQuanZhengHei.ttf";
/**
* 历史数据清除开关0关闭1开启
*/
private Integer historyDataClearEnable = 0;
/**
* 一分钟内接口请求次数限制开关0关闭1开启
*/
private Integer reqFrequencyLimitEnable = 0;
/**
* 一分钟内验证码最多失败次数限制默认5次
*/
private int reqGetLockLimit = 5;
/**
* 一分钟内验证码最多失败次数限制达标后锁定时间默认300秒
*/
private int reqGetLockSeconds = 300;
/**
* 获取验证码接口一分钟内请求次数限制默认100次
*/
private int reqGetMinuteLimit = 100;
/**
* 校验检验码接口一分内请求次数限制默认100次
*/
private int reqCheckMinuteLimit = 100;
/**
* 二次校验检验码接口一分钟内请求次数限制默认100次
*/
private int reqVerifyMinuteLimit = 100;
/**
* local缓存的阈值默认1000个
*/
private String cacheNumber = "1000";
/**
* 定时清理过期local缓存默认180秒
*/
private String timingClear = "180";
/**
* 右下角水印文字
*/
private String waterMark = "我的水印";
/**
* 右下角水印字体(默认:文泉驿正黑)
*/
private String waterFont = "WenQuanZhengHei.ttf";
/**
* 滑块干扰项默认0
*/
private String interferenceOptions = "0";
/**
* 点选字体样式默认BOLD
*/
private int fontStyle = Font.BOLD;
/**
* 点选字体大小默认25
*/
private int fontSize = 25;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.captcha.behavior.enums;
import lombok.Getter;
/**
* 缓存类型枚举
*
* @author Bull-BCLS
* @since 1.1.0
*/
@Getter
public enum StorageType {
/**
* 内存
*/
LOCAL,
/**
* Redis
*/
REDIS,
/**
* 自定义
*/
CUSTOM,
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.captcha.behavior.impl;
import com.anji.captcha.service.CaptchaCacheService;
import top.charles7c.continew.starter.cache.redisson.util.RedisUtils;
import top.charles7c.continew.starter.captcha.behavior.enums.StorageType;
import java.time.Duration;
/**
* 行为验证码 Redis 缓存实现
*
* @author Bull-BCLS
* @since 1.1.0
*/
public class BehaviorCaptchaCacheServiceImpl implements CaptchaCacheService {
@Override
public void set(String key, String value, long expiresInSeconds) {
RedisUtils.set(key, value, Duration.ofSeconds(expiresInSeconds));
}
@Override
public boolean exists(String key) {
return RedisUtils.hasKey(key);
}
@Override
public void delete(String key) {
RedisUtils.delete(key);
}
@Override
public String get(String key) {
return RedisUtils.get(key);
}
@Override
public String type() {
return StorageType.REDIS.name().toLowerCase();
}
}

View File

@@ -0,0 +1 @@
top.charles7c.continew.starter.captcha.behavior.autoconfigure.BehaviorCaptchaAutoConfiguration

View File

@@ -21,7 +21,7 @@ import cn.hutool.core.util.StrUtil;
import com.wf.captcha.base.Captcha; import com.wf.captcha.base.Captcha;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import top.charles7c.continew.starter.captcha.graphic.enums.GraphicCaptchaTypeEnum; import top.charles7c.continew.starter.captcha.graphic.enums.GraphicCaptchaType;
import java.awt.*; import java.awt.*;
@@ -43,7 +43,7 @@ public class GraphicCaptchaProperties {
/** /**
* 类型 * 类型
*/ */
private GraphicCaptchaTypeEnum type; private GraphicCaptchaType type;
/** /**
* 内容长度 * 内容长度

View File

@@ -29,7 +29,7 @@ import lombok.RequiredArgsConstructor;
*/ */
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public enum GraphicCaptchaTypeEnum { public enum GraphicCaptchaType {
/** /**
* 算术 * 算术

View File

@@ -17,6 +17,7 @@
<modules> <modules>
<module>continew-starter-captcha-graphic</module> <module>continew-starter-captcha-graphic</module>
<module>continew-starter-captcha-behavior</module>
</modules> </modules>
<dependencies> <dependencies>

View File

@@ -62,6 +62,7 @@
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<redisson.version>3.25.2</redisson.version> <redisson.version>3.25.2</redisson.version>
<sms4j.version>3.0.4</sms4j.version> <sms4j.version>3.0.4</sms4j.version>
<aj-captcha.version>1.3.0</aj-captcha.version>
<easy-captcha.version>1.6.2</easy-captcha.version> <easy-captcha.version>1.6.2</easy-captcha.version>
<easy-excel.version>3.3.3</easy-excel.version> <easy-excel.version>3.3.3</easy-excel.version>
<knife4j.version>4.4.0</knife4j.version> <knife4j.version>4.4.0</knife4j.version>
@@ -149,6 +150,13 @@
<version>${sms4j.version}</version> <version>${sms4j.version}</version>
</dependency> </dependency>
<!-- AJ-Captcha行为验证码包含滑动拼图、文字点选两种方式UI支持弹出和嵌入两种方式 -->
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha</artifactId>
<version>${aj-captcha.version}</version>
</dependency>
<!-- Easy CaptchaJava 图形验证码,支持 gif、中文、算术等类型可用于 Java Web、JavaSE 等项目) --> <!-- Easy CaptchaJava 图形验证码,支持 gif、中文、算术等类型可用于 Java Web、JavaSE 等项目) -->
<dependency> <dependency>
<groupId>com.github.whvcse</groupId> <groupId>com.github.whvcse</groupId>
@@ -243,6 +251,13 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 验证码模块 - 行为验证码 -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-starter-captcha-behavior</artifactId>
<version>${revision}</version>
</dependency>
<!-- 验证码模块 - 图形验证码 --> <!-- 验证码模块 - 图形验证码 -->
<dependency> <dependency>
<groupId>top.charles7c.continew</groupId> <groupId>top.charles7c.continew</groupId>