mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-08 07:01:37 +08:00
feat(captcha): 新增行为验证码自动配置
This commit is contained in:
@@ -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>
|
@@ -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.");
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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,
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
top.charles7c.continew.starter.captcha.behavior.autoconfigure.BehaviorCaptchaAutoConfiguration
|
Reference in New Issue
Block a user