diff --git a/pom.xml b/pom.xml index 14c6562..315b921 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ tianai-captcha tianai-captcha-springboot-starter tianai-captcha-solon-plugin + tianai-captcha-springboot4-starter diff --git a/tianai-captcha-springboot4-starter/pom.xml b/tianai-captcha-springboot4-starter/pom.xml new file mode 100644 index 0000000..92e5851 --- /dev/null +++ b/tianai-captcha-springboot4-starter/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + cloud.tianai.captcha + tianai-captcha-parent + ${revision} + + + tianai-captcha-springboot4-starter + + + 17 + 17 + UTF-8 + 3.18.0 + + + + + org.springframework.boot + spring-boot-dependencies + 4.0.0 + pom + import + true + + + + + + + org.springframework.boot + spring-boot-autoconfigure + compile + true + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-data-redis + compile + true + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + com.google.code.gson + gson + + + org.projectlombok + lombok + true + + + cloud.tianai.captcha + tianai-captcha + ${revision} + compile + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 4.0.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + -parameters + + + + + \ No newline at end of file diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/CacheStoreAutoConfiguration.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/CacheStoreAutoConfiguration.java new file mode 100644 index 0000000..7f1c37f --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/CacheStoreAutoConfiguration.java @@ -0,0 +1,63 @@ +package cloud.tianai.captcha.spring4.autoconfiguration; + +import cloud.tianai.captcha.cache.CacheStore; +import cloud.tianai.captcha.cache.impl.LocalCacheStore; +import cloud.tianai.captcha.spring4.store.impl.RedisCacheStore; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 缓存存储器的自动配置类 + * + * @author lichenpark + */ +@AutoConfigureAfter({DataRedisAutoConfiguration.class}) +@Configuration(proxyBeanMethods = false) +public class CacheStoreAutoConfiguration { + + /** + * RedisCacheStoreConfiguration + * + * @author 天爱有情 + * @since 2020/10/27 14:06 + */ + @Order(1) + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(StringRedisTemplate.class) + public static class RedisCacheStoreConfiguration { + + @Bean + @ConditionalOnBean(StringRedisTemplate.class) + @ConditionalOnMissingBean(CacheStore.class) + public CacheStore redis(StringRedisTemplate redisTemplate) { + return new RedisCacheStore(redisTemplate); + } + + } + + /** + * LocalCacheStoreConfiguration + * + * @author 天爱有情 + * @since 2020/10/27 14:06 + */ + @Order(2) + @Configuration(proxyBeanMethods = false) + public static class LocalCacheStoreConfiguration { + + @Bean + @ConditionalOnMissingBean(CacheStore.class) + public CacheStore local() { + return new LocalCacheStore(); + } + + } + +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/ImageCaptchaAutoConfiguration.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/ImageCaptchaAutoConfiguration.java new file mode 100644 index 0000000..7e0e917 --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/ImageCaptchaAutoConfiguration.java @@ -0,0 +1,131 @@ +package cloud.tianai.captcha.spring4.autoconfiguration; + + +import cloud.tianai.captcha.application.ImageCaptchaApplication; +import cloud.tianai.captcha.application.TACBuilder; +import cloud.tianai.captcha.cache.CacheStore; +import cloud.tianai.captcha.common.util.CollectionUtils; +import cloud.tianai.captcha.generator.ImageCaptchaGenerator; +import cloud.tianai.captcha.generator.ImageTransform; +import cloud.tianai.captcha.generator.impl.transform.Base64ImageTransform; +import cloud.tianai.captcha.interceptor.CaptchaInterceptor; +import cloud.tianai.captcha.interceptor.CaptchaInterceptorGroup; +import cloud.tianai.captcha.interceptor.impl.ParamCheckCaptchaInterceptor; +import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; +import cloud.tianai.captcha.resource.ResourceProviders; +import cloud.tianai.captcha.resource.ResourceStore; +import cloud.tianai.captcha.resource.impl.DefaultImageCaptchaResourceManager; +import cloud.tianai.captcha.resource.impl.LocalMemoryResourceStore; +import cloud.tianai.captcha.spring4.plugins.SpringMultiImageCaptchaGenerator; +import cloud.tianai.captcha.spring4.plugins.secondary.SecondaryVerificationApplication; +import cloud.tianai.captcha.validator.ImageCaptchaValidator; +import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.core.annotation.Order; + +/** + * @Author: 天爱有情 + * @Date 2020/5/29 9:49 + * @Description 滑块验证码自动装配 + */ +@Slf4j +@Order +@Configuration +@AutoConfigureAfter(CacheStoreAutoConfiguration.class) +@EnableConfigurationProperties({SpringImageCaptchaProperties.class}) +public class ImageCaptchaAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public ResourceStore resourceStore() { + return new LocalMemoryResourceStore(); + } + + @Bean + @ConditionalOnMissingBean + public ImageCaptchaResourceManager imageCaptchaResourceManager(ResourceStore resourceStore) { + ResourceProviders resourceProviders = new ResourceProviders(); + return new DefaultImageCaptchaResourceManager(resourceStore, resourceProviders); + } + + @Bean + @ConditionalOnMissingBean + public ImageTransform imageTransform() { + return new Base64ImageTransform(); + } + + + @Bean + @ConditionalOnMissingBean + public ImageCaptchaGenerator imageCaptchaTemplate(SpringImageCaptchaProperties prop, + ImageCaptchaResourceManager captchaResourceManager, + ImageTransform imageTransform, + BeanFactory beanFactory) { + return new SpringMultiImageCaptchaGenerator(captchaResourceManager, imageTransform, beanFactory); + } + + @Bean + @ConditionalOnMissingBean + public ImageCaptchaValidator imageCaptchaValidator() { + return new SimpleImageCaptchaValidator(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + @ConditionalOnMissingBean + public CaptchaInterceptor captchaInterceptor() { + CaptchaInterceptorGroup group = new CaptchaInterceptorGroup(); + group.addInterceptor(new ParamCheckCaptchaInterceptor()); +// group.addInterceptor(new BasicTrackCaptchaInterceptor()); + return group; + } + + + @Bean + @ConditionalOnMissingBean + public ImageCaptchaApplication imageCaptchaApplication(ImageCaptchaGenerator captchaGenerator, + ImageCaptchaValidator imageCaptchaValidator, + CacheStore cacheStore, + ResourceStore resourceStore, + SpringImageCaptchaProperties prop, + CaptchaInterceptor captchaInterceptor, + ApplicationContext applicationContext) { + TACBuilder tacBuilder = TACBuilder.builder(resourceStore) + .setGenerator(captchaGenerator) + .setValidator(imageCaptchaValidator) + .setCacheStore(cacheStore) + .setProp(prop) + .setInterceptor(captchaInterceptor); + + if (prop.getInitDefaultResource()) { + tacBuilder.addDefaultTemplate(prop.getDefaultResourcePrefix()); + } + if (!CollectionUtils.isEmpty(prop.getFontPath())) { + // 读取字体包 + for (String fontPath : prop.getFontPath()) { + int index = fontPath.indexOf(":"); + String[] split = index > 0 ? new String[]{fontPath.substring(0, index), fontPath.substring(index + 1)} : new String[]{"", fontPath}; + String type = split[0]; + String path = split[1]; + tacBuilder.addFont(new cloud.tianai.captcha.resource.common.model.dto.Resource(type, path)); + } + } + ImageCaptchaApplication target = tacBuilder.build(); + if (prop.getSecondary() != null && Boolean.TRUE.equals(prop.getSecondary().getEnabled())) { + // 一个简单的二次验证 + target = new SecondaryVerificationApplication(target, prop.getSecondary()); + } + return target; + } + +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/SecondaryVerificationProperties.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/SecondaryVerificationProperties.java new file mode 100644 index 0000000..df199f2 --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/SecondaryVerificationProperties.java @@ -0,0 +1,12 @@ +package cloud.tianai.captcha.spring4.autoconfiguration; + +import lombok.Data; + +@Data +public class SecondaryVerificationProperties { + + private Boolean enabled = false; + private Long expire = 120000L; + private String keyPrefix = "captcha:secondary"; + +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/SpringImageCaptchaProperties.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/SpringImageCaptchaProperties.java new file mode 100644 index 0000000..c568c6c --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/autoconfiguration/SpringImageCaptchaProperties.java @@ -0,0 +1,32 @@ +package cloud.tianai.captcha.spring4.autoconfiguration; + +import cloud.tianai.captcha.application.ImageCaptchaProperties; +import cloud.tianai.captcha.resource.DefaultBuiltInResources; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * @Author: 天爱有情 + * @date 2020/10/19 18:41 + * @Description 滑块验证码属性 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ConfigurationProperties(prefix = "captcha") +public class SpringImageCaptchaProperties extends ImageCaptchaProperties { + /** 是否初始化默认资源. */ + private Boolean initDefaultResource = false; + /** 默认资源的位置. */ + private String defaultResourcePrefix = DefaultBuiltInResources.PATH_PREFIX; + /** 字体包路径. */ + private List fontPath; + /** 二次验证配置. */ + @NestedConfigurationProperty + private SecondaryVerificationProperties secondary; +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/exception/CaptchaValidException.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/exception/CaptchaValidException.java new file mode 100644 index 0000000..4b9a11b --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/exception/CaptchaValidException.java @@ -0,0 +1,41 @@ +package cloud.tianai.captcha.spring4.exception; + +import cloud.tianai.captcha.common.exception.ImageCaptchaException; +import lombok.Getter; +import lombok.Setter; + +/** + * @Author: 天爱有情 + * @Date 2020/6/19 16:36 + * @Description 验证码验证失败异常 + */ +@Getter +@Setter +public class CaptchaValidException extends ImageCaptchaException { + + private String captchaType; + private Integer code; + public CaptchaValidException() { + } + + public CaptchaValidException(String captchaType,String message) { + super(message); + this.captchaType = captchaType; + } + public CaptchaValidException(String captchaType,Integer code, String message) { + super(message); + this.code = code; + this.captchaType = captchaType; + } + public CaptchaValidException(String message, Throwable cause) { + super(message, cause); + } + + public CaptchaValidException(Throwable cause) { + super(cause); + } + + public CaptchaValidException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/plugins/SpringMultiImageCaptchaGenerator.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/plugins/SpringMultiImageCaptchaGenerator.java new file mode 100644 index 0000000..c504561 --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/plugins/SpringMultiImageCaptchaGenerator.java @@ -0,0 +1,36 @@ +package cloud.tianai.captcha.spring4.plugins; + +import cloud.tianai.captcha.generator.ImageCaptchaGeneratorProvider; +import cloud.tianai.captcha.generator.ImageTransform; +import cloud.tianai.captcha.generator.impl.MultiImageCaptchaGenerator; +import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; + +/** + * @Author: 天爱有情 + * @date 2022/5/19 14:37 + * @Description 基于spring的 多验证码生成器 + */ +public class SpringMultiImageCaptchaGenerator extends MultiImageCaptchaGenerator { + private ListableBeanFactory beanFactory; + + public SpringMultiImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, + BeanFactory beanFactory) { + super(imageCaptchaResourceManager, imageTransform); + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + @Override + protected void doInit() { + super.doInit(); + String[] beanNamesForType = beanFactory.getBeanNamesForType(ImageCaptchaGeneratorProvider.class); + if (!ArrayUtils.isEmpty(beanNamesForType)) { + for (String beanName : beanNamesForType) { + ImageCaptchaGeneratorProvider provider = beanFactory.getBean(beanName, ImageCaptchaGeneratorProvider.class); + addImageCaptchaGeneratorProvider(provider); + } + } + } +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/plugins/secondary/SecondaryVerificationApplication.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/plugins/secondary/SecondaryVerificationApplication.java new file mode 100644 index 0000000..f2e0723 --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/plugins/secondary/SecondaryVerificationApplication.java @@ -0,0 +1,58 @@ +package cloud.tianai.captcha.spring4.plugins.secondary; + +import cloud.tianai.captcha.application.FilterImageCaptchaApplication; +import cloud.tianai.captcha.application.ImageCaptchaApplication; +import cloud.tianai.captcha.common.AnyMap; +import cloud.tianai.captcha.common.response.ApiResponse; +import cloud.tianai.captcha.spring4.autoconfiguration.SecondaryVerificationProperties; +import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @Author: 天爱有情 + * @date 2022/3/2 14:16 + * @Description 二次验证 + */ +public class SecondaryVerificationApplication extends FilterImageCaptchaApplication { + private SecondaryVerificationProperties prop; + + public SecondaryVerificationApplication(ImageCaptchaApplication target, SecondaryVerificationProperties prop) { + super(target); + this.prop = prop; + } + + @Override + public ApiResponse matching(String id, ImageCaptchaTrack imageCaptchaTrack) { + ApiResponse match = super.matching(id, imageCaptchaTrack); + if (match.isSuccess()) { + // 如果匹配成功, 添加二次验证记录 + addSecondaryVerification(id, imageCaptchaTrack); + } + return match; + } + + /** + * 二次缓存验证 + * @param id id + * @return boolean + */ + public boolean secondaryVerification(String id) { + Map cache = target.getCacheStore().getAndRemoveCache(getKey(id)); + 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); + } +} diff --git a/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/store/impl/RedisCacheStore.java b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/store/impl/RedisCacheStore.java new file mode 100644 index 0000000..3f42c4b --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/java/cloud/tianai/captcha/spring4/store/impl/RedisCacheStore.java @@ -0,0 +1,71 @@ +package cloud.tianai.captcha.spring4.store.impl; + +import cloud.tianai.captcha.cache.CacheStore; +import cloud.tianai.captcha.common.AnyMap; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +/** + * @Author: 天爱有情 + * @date 2022/3/2 14:42 + * @Description redis实现的缓存 + */ +public class RedisCacheStore implements CacheStore { + + private static final RedisScript SCRIPT_GET_CACHE = new DefaultRedisScript<>("local res = redis.call('get',KEYS[1]) if res == nil then return nil else redis.call('del',KEYS[1]) return res end", String.class); + protected StringRedisTemplate redisTemplate; + private Gson gson = new Gson(); + + public RedisCacheStore(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Override + public AnyMap getCache(String key) { + String jsonData = redisTemplate.opsForValue().get(key); + if (StringUtils.isEmpty(jsonData)) { + return null; + } + return gson.fromJson(jsonData, new TypeToken() { + }.getType()); + } + + @Override + public AnyMap getAndRemoveCache(String key) { + String json = redisTemplate.execute(SCRIPT_GET_CACHE, Collections.singletonList(key)); + if (org.apache.commons.lang3.StringUtils.isBlank(json)) { + return null; + } + return gson.fromJson(json, new TypeToken() { + }.getType()); + } + + @Override + public boolean setCache(String key, AnyMap data, Long expire, TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, gson.toJson(data), expire, timeUnit); + return true; + } + + @Override + public Long incr(String key, long delta, Long expire, TimeUnit timeUnit) { + Long increment = redisTemplate.opsForValue().increment(key, delta); + redisTemplate.expire(key, expire, timeUnit); + return increment; + } + + @Override + public Long getLong(String key) { + String value = redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } + return Long.valueOf(value); + } +} diff --git a/tianai-captcha-springboot4-starter/src/main/resources/META-INF/spring.factories b/tianai-captcha-springboot4-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..2279330 --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cloud.tianai.captcha.spring4.autoconfiguration.CacheStoreAutoConfiguration,\ + cloud.tianai.captcha.spring4.autoconfiguration.ImageCaptchaAutoConfiguration \ No newline at end of file diff --git a/tianai-captcha-springboot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/tianai-captcha-springboot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..fb1e5de --- /dev/null +++ b/tianai-captcha-springboot4-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +cloud.tianai.captcha.spring4.autoconfiguration.CacheStoreAutoConfiguration +cloud.tianai.captcha.spring4.autoconfiguration.ImageCaptchaAutoConfiguration