mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-05-06 21:53:10 +08:00
solon插件暂时无人维护,先考虑单独搬迁项目到另一个仓库
This commit is contained in:
@@ -12,11 +12,10 @@
|
||||
<modules>
|
||||
<module>tianai-captcha</module>
|
||||
<module>tianai-captcha-springboot-starter</module>
|
||||
<module>tianai-captcha-solon-plugin</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<revision>1.5.4</revision>
|
||||
<revision>1.5.5</revision>
|
||||
<java.version>1.8</java.version>
|
||||
<!-- 打包跳过单元测试 -->
|
||||
<skipTests>true</skipTests>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cloud.tianai.captcha</groupId>
|
||||
<artifactId>tianai-captcha-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<artifactId>tianai-captcha-solon-plugin</artifactId>
|
||||
<name>tianai-captcha-solon-plugin</name>
|
||||
<description>行为验证码的solon插件</description>
|
||||
<url>https://gitee.com/tianai/tianai-captcha-solon-plugin</url>
|
||||
|
||||
<properties>
|
||||
<solon.version>3.5.2</solon.version>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<java.version>1.8</java.version>
|
||||
<!-- 打包跳过单元测试 -->
|
||||
<skipTests>true</skipTests>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon</artifactId>
|
||||
<version>${solon.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-web</artifactId>
|
||||
<version>${solon.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cloud.tianai.captcha</groupId>
|
||||
<artifactId>tianai-captcha</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
<compilerArgument>-parameters</compilerArgument>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,21 +0,0 @@
|
||||
package cloud.tianai.captcha.solon;
|
||||
|
||||
import cloud.tianai.captcha.solon.config.ImageCaptchaAutoConfiguration;
|
||||
import cloud.tianai.captcha.solon.properties.CaptchaProperties;
|
||||
import org.noear.solon.core.AppContext;
|
||||
import org.noear.solon.core.Plugin;
|
||||
|
||||
/**
|
||||
* 插件启动类
|
||||
* @Author XT
|
||||
* @Date 2024.09.03
|
||||
*/
|
||||
public class XPluginImp implements Plugin {
|
||||
|
||||
@Override
|
||||
public void start(AppContext context) {
|
||||
context.beanMake(CaptchaProperties.class);
|
||||
context.beanMake(ImageCaptchaAutoConfiguration.class);
|
||||
}
|
||||
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.config;
|
||||
|
||||
import cloud.tianai.captcha.application.ImageCaptchaApplication;
|
||||
import cloud.tianai.captcha.application.TACBuilder;
|
||||
import cloud.tianai.captcha.cache.CacheStore;
|
||||
import cloud.tianai.captcha.cache.impl.LocalCacheStore;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.solon.plugins.secondary.SecondaryVerificationApplication;
|
||||
import cloud.tianai.captcha.solon.properties.CaptchaProperties;
|
||||
import cloud.tianai.captcha.solon.service.CaptchaRedisCacheService;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author XT
|
||||
* @Date 2024.09.03
|
||||
*/
|
||||
@Configuration
|
||||
public class ImageCaptchaAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ImageCaptchaApplication imageCaptchaApplication(CaptchaProperties captchaProperties, @Inject(required = false) CaptchaRedisCacheService cacheService) {
|
||||
TACBuilder tacBuilder = TACBuilder.builder();
|
||||
tacBuilder.addDefaultTemplate();
|
||||
tacBuilder.expire("default", captchaProperties.getExpire());
|
||||
tacBuilder.prefix(captchaProperties.getPrefix());
|
||||
// 注入背景图资源
|
||||
if (captchaProperties.getResources().getAuto()) {
|
||||
String[] split = captchaProperties.getResources().getAutoType().split(",");
|
||||
List<String> wordImageClickList = captchaProperties.getResources().getImages();
|
||||
for (String type : split) {
|
||||
for (String path : wordImageClickList) {
|
||||
tacBuilder.addResource(type, new Resource("classpath", path));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> wordImageClickList = captchaProperties.getResources().getWORD_IMAGE_CLICK();
|
||||
if (!wordImageClickList.isEmpty()) {
|
||||
for (String path : wordImageClickList) {
|
||||
tacBuilder.addResource("WORD_IMAGE_CLICK", new Resource("classpath", path));
|
||||
}
|
||||
}
|
||||
List<String> concatList = captchaProperties.getResources().getCONCAT();
|
||||
if (!concatList.isEmpty()) {
|
||||
for (String path : concatList) {
|
||||
tacBuilder.addResource("CONCAT", new Resource("classpath", path));
|
||||
}
|
||||
}
|
||||
List<String> sliderList = captchaProperties.getResources().getSLIDER();
|
||||
if (!sliderList.isEmpty()) {
|
||||
for (String path : sliderList) {
|
||||
tacBuilder.addResource("SLIDER", new Resource("classpath", path));
|
||||
}
|
||||
}
|
||||
List<String> rotateList = captchaProperties.getResources().getROTATE();
|
||||
if (!rotateList.isEmpty()) {
|
||||
for (String path : rotateList) {
|
||||
tacBuilder.addResource("ROTATE", new Resource("classpath", path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注入字体包
|
||||
if (null != captchaProperties.getFontPath()) {
|
||||
List<String> fontPathList = captchaProperties.getFontPath();
|
||||
if (!fontPathList.isEmpty()) {
|
||||
for (String path : fontPathList) {
|
||||
try {
|
||||
tacBuilder.addFont(new Resource("classpath", path));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("读取字体包失败,path=" + path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CacheStore cacheStore = cacheService;
|
||||
// 注入缓存器
|
||||
if (null == cacheStore) {
|
||||
cacheStore = new LocalCacheStore();
|
||||
}
|
||||
tacBuilder.setCacheStore(cacheStore);
|
||||
ImageCaptchaApplication target = tacBuilder.build();
|
||||
|
||||
// 二次验证
|
||||
if (captchaProperties.getSecondary().getEnabled()) {
|
||||
target = new SecondaryVerificationApplication(target, captchaProperties, cacheStore);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.plugins.secondary;
|
||||
|
||||
import cloud.tianai.captcha.application.FilterImageCaptchaApplication;
|
||||
import cloud.tianai.captcha.application.ImageCaptchaApplication;
|
||||
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
|
||||
import cloud.tianai.captcha.cache.CacheStore;
|
||||
import cloud.tianai.captcha.common.AnyMap;
|
||||
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
|
||||
import cloud.tianai.captcha.common.response.ApiResponse;
|
||||
import cloud.tianai.captcha.solon.properties.CaptchaLimit;
|
||||
import cloud.tianai.captcha.solon.properties.CaptchaProperties;
|
||||
import cloud.tianai.captcha.solon.properties.CaptchaSecondary;
|
||||
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Author XT
|
||||
* @Date 2024.09.03
|
||||
*/
|
||||
public class SecondaryVerificationApplication extends FilterImageCaptchaApplication {
|
||||
|
||||
private final CaptchaSecondary prop;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final CacheStore redisCacheService;
|
||||
|
||||
public SecondaryVerificationApplication(ImageCaptchaApplication target, CaptchaProperties captchaProperties, CacheStore redisCacheService) {
|
||||
super(target);
|
||||
this.captchaProperties = captchaProperties;
|
||||
this.prop = captchaProperties.getSecondary();
|
||||
this.redisCacheService = redisCacheService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(String type) {
|
||||
// 检查是否每分钟超过限制
|
||||
CaptchaLimit limit = captchaProperties.getLimit();
|
||||
if (null != limit && limit.getEnable()) {
|
||||
Context current = Context.current();
|
||||
String errLimitKey = getLimitKey(current, "error");
|
||||
Long errLimit = redisCacheService.getLong(errLimitKey);
|
||||
if (null != errLimit && errLimit >= limit.getErrorLimit()) {
|
||||
throw new ImageCaptchaException("验证次数过多,请稍后再试");
|
||||
}
|
||||
String reqLimitKey = getLimitKey(current, "req");
|
||||
Long reqLimit = redisCacheService.getLong(reqLimitKey);
|
||||
if (null != reqLimit && reqLimit >= limit.getReqLimit()) {
|
||||
throw new ImageCaptchaException("获取验证码频繁,请稍后再试");
|
||||
}
|
||||
redisCacheService.incr(reqLimitKey, 1, 60L, TimeUnit.SECONDS);
|
||||
}
|
||||
return super.generateCaptcha(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<?> matching(String id, ImageCaptchaTrack imageCaptchaTrack) {
|
||||
ApiResponse<?> match = super.matching(id, imageCaptchaTrack);
|
||||
if (match.isSuccess()) {
|
||||
// 如果匹配成功, 添加二次验证记录
|
||||
addSecondaryVerification(id + getRemoteId(Context.current()), imageCaptchaTrack);
|
||||
} else {
|
||||
CaptchaLimit limit = captchaProperties.getLimit();
|
||||
if (null != limit && limit.getEnable()) {
|
||||
Context current = Context.current();
|
||||
String limitKey = getLimitKey(current, "error");
|
||||
redisCacheService.incr(limitKey, 1, 60L, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二次缓存验证
|
||||
* @param id id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean secondaryVerification(String id) {
|
||||
Map<String, Object> cache = target.getCacheStore().getAndRemoveCache(getKey(id + getRemoteId(Context.current())));
|
||||
return cache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加二次缓存验证记录
|
||||
* @param id id
|
||||
* @param imageCaptchaTrack sliderCaptchaTrack
|
||||
*/
|
||||
protected void addSecondaryVerification(String id, ImageCaptchaTrack imageCaptchaTrack) {
|
||||
target.getCacheStore().setCache(getKey(id), new AnyMap(), prop.getExpire(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
protected String getKey(String id) {
|
||||
return prop.getKeyPrefix().concat(":").concat(id);
|
||||
}
|
||||
|
||||
protected String getLimitKey(Context ctx, String type) {
|
||||
return prop.getKeyPrefix().concat(":limit:")
|
||||
.concat(type)
|
||||
.concat(":")
|
||||
.concat(getRemoteId(ctx));
|
||||
}
|
||||
|
||||
|
||||
public static String getRemoteId(Context ctx) {
|
||||
return ctx.realIp() + ctx.userAgent();
|
||||
}
|
||||
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.properties;
|
||||
|
||||
/**
|
||||
* @Author XT
|
||||
* @Date 2024.09.04
|
||||
*/
|
||||
public class CaptchaLimit {
|
||||
|
||||
private Boolean enable;
|
||||
|
||||
private Long reqLimit;
|
||||
|
||||
private Long errorLimit;
|
||||
|
||||
public Boolean getEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
public void setEnable(Boolean enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public Long getReqLimit() {
|
||||
return reqLimit;
|
||||
}
|
||||
|
||||
public void setReqLimit(Long reqLimit) {
|
||||
this.reqLimit = reqLimit;
|
||||
}
|
||||
|
||||
public Long getErrorLimit() {
|
||||
return errorLimit;
|
||||
}
|
||||
|
||||
public void setErrorLimit(Long errorLimit) {
|
||||
this.errorLimit = errorLimit;
|
||||
}
|
||||
}
|
||||
-93
@@ -1,93 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.properties;
|
||||
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author XT
|
||||
* @Date 2024.09.03
|
||||
*/
|
||||
@Inject("${captcha}")
|
||||
@Configuration
|
||||
public class CaptchaProperties {
|
||||
|
||||
/**
|
||||
* redis 前缀
|
||||
*/
|
||||
private String prefix;
|
||||
|
||||
/**
|
||||
* 有效期
|
||||
*/
|
||||
private Long expire;
|
||||
|
||||
/**
|
||||
* 字体路径
|
||||
*/
|
||||
private List<String> fontPath;
|
||||
|
||||
/**
|
||||
* 资源路径
|
||||
*/
|
||||
private CaptchaResource resources;
|
||||
|
||||
/**
|
||||
* 二次验证
|
||||
*/
|
||||
private CaptchaSecondary secondary;
|
||||
|
||||
/**
|
||||
* 每分限流
|
||||
*/
|
||||
private CaptchaLimit limit;
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public Long getExpire() {
|
||||
return expire;
|
||||
}
|
||||
|
||||
public void setExpire(Long expire) {
|
||||
this.expire = expire;
|
||||
}
|
||||
|
||||
public List<String> getFontPath() {
|
||||
return fontPath;
|
||||
}
|
||||
|
||||
public void setFontPath(List<String> fontPath) {
|
||||
this.fontPath = fontPath;
|
||||
}
|
||||
|
||||
public CaptchaResource getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public void setResources(CaptchaResource resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public CaptchaSecondary getSecondary() {
|
||||
return secondary;
|
||||
}
|
||||
|
||||
public void setSecondary(CaptchaSecondary secondary) {
|
||||
this.secondary = secondary;
|
||||
}
|
||||
|
||||
public CaptchaLimit getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(CaptchaLimit limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.properties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author XT
|
||||
* @Date 2024.09.03
|
||||
*/
|
||||
public class CaptchaResource {
|
||||
|
||||
private Boolean auto;
|
||||
|
||||
private String autoType;
|
||||
|
||||
private List<String> images;
|
||||
|
||||
private List<String> SLIDER;
|
||||
|
||||
private List<String> WORD_IMAGE_CLICK;
|
||||
|
||||
private List<String> ROTATE;
|
||||
|
||||
private List<String> CONCAT;
|
||||
|
||||
public List<String> getSLIDER() {
|
||||
return SLIDER;
|
||||
}
|
||||
|
||||
public void setSLIDER(List<String> SLIDER) {
|
||||
this.SLIDER = SLIDER;
|
||||
}
|
||||
|
||||
public List<String> getWORD_IMAGE_CLICK() {
|
||||
return WORD_IMAGE_CLICK;
|
||||
}
|
||||
|
||||
public void setWORD_IMAGE_CLICK(List<String> WORD_IMAGE_CLICK) {
|
||||
this.WORD_IMAGE_CLICK = WORD_IMAGE_CLICK;
|
||||
}
|
||||
|
||||
public List<String> getROTATE() {
|
||||
return ROTATE;
|
||||
}
|
||||
|
||||
public void setROTATE(List<String> ROTATE) {
|
||||
this.ROTATE = ROTATE;
|
||||
}
|
||||
|
||||
public List<String> getCONCAT() {
|
||||
return CONCAT;
|
||||
}
|
||||
|
||||
public void setCONCAT(List<String> CONCAT) {
|
||||
this.CONCAT = CONCAT;
|
||||
}
|
||||
|
||||
public Boolean getAuto() {
|
||||
return auto;
|
||||
}
|
||||
|
||||
public void setAuto(Boolean auto) {
|
||||
this.auto = auto;
|
||||
}
|
||||
|
||||
public String getAutoType() {
|
||||
return autoType;
|
||||
}
|
||||
|
||||
public void setAutoType(String autoType) {
|
||||
this.autoType = autoType;
|
||||
}
|
||||
|
||||
public List<String> getImages() {
|
||||
return images;
|
||||
}
|
||||
|
||||
public void setImages(List<String> images) {
|
||||
this.images = images;
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.properties;
|
||||
|
||||
/**
|
||||
* @Author XT
|
||||
* @Date 2024.09.03
|
||||
*/
|
||||
public class CaptchaSecondary {
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private Long expire;
|
||||
|
||||
private String keyPrefix;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Long getExpire() {
|
||||
return expire;
|
||||
}
|
||||
|
||||
public void setExpire(Long expire) {
|
||||
this.expire = expire;
|
||||
}
|
||||
|
||||
public String getKeyPrefix() {
|
||||
return keyPrefix;
|
||||
}
|
||||
|
||||
public void setKeyPrefix(String keyPrefix) {
|
||||
this.keyPrefix = keyPrefix;
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package cloud.tianai.captcha.solon.service;
|
||||
|
||||
import cloud.tianai.captcha.cache.CacheStore;
|
||||
|
||||
public interface CaptchaRedisCacheService extends CacheStore {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user