diff --git a/core/captcha-plus/pom.xml b/core/captcha-plus/pom.xml
index 2338436f0f58889289ccfdb5c9da01073f1a5dcd..c03a41004eda601fe316fffa2c5a7dd2cfd60620 100644
--- a/core/captcha-plus/pom.xml
+++ b/core/captcha-plus/pom.xml
@@ -39,7 +39,12 @@
1.7.36
provided
-
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.2
+ test
+
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/model/common/Const.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/model/common/Const.java
index 602aa4362d7428d3b565064c65f9aa04eac56bb4..6992584f0c073d85db2f5c614dbd91928cf14687 100644
--- a/core/captcha-plus/src/main/java/com/xingyuv/captcha/model/common/Const.java
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/model/common/Const.java
@@ -25,6 +25,8 @@ public interface Const {
*/
String CAPTCHA_CACHE_TYPE = "captcha.cacheType";
+ String CAPTCHA_DECRYPT_TYPE = "captcha.decrypt.type";
+
/**
* 右下角水印文字(我的水印)
*/
@@ -59,7 +61,9 @@ public interface Const {
/**
* aes加密开关
+ * @deprecated 不再需要这个参数,当解密算法为 AES 时,会自动开启加密开关
*/
+ @Deprecated
String CAPTCHA_AES_STATUS = "captcha.aes.status";
/**
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/CaptchaDecryptService.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/CaptchaDecryptService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1af27a67dd97d94f8e1700d939ca79ecb21d8449
--- /dev/null
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/CaptchaDecryptService.java
@@ -0,0 +1,17 @@
+package com.xingyuv.captcha.service;
+
+/**
+ * 用于验证码安全验证
+ */
+public interface CaptchaDecryptService {
+ /**
+ * 解密
+ */
+ String decrypt(String point, String key) throws Exception;
+
+ /**
+ * 用于区别算法,不区分大小写
+ * @return
+ */
+ String getType();
+}
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/AbstractCaptchaService.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/AbstractCaptchaService.java
index 4da93c5e975d191679b3a98a71a26d752641d243..53b916b671408dddfca954e0592ae666ba0a01e1 100644
--- a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/AbstractCaptchaService.java
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/AbstractCaptchaService.java
@@ -11,6 +11,7 @@ import com.xingyuv.captcha.model.common.RepCodeEnum;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaCacheService;
+import com.xingyuv.captcha.service.CaptchaDecryptService;
import com.xingyuv.captcha.service.CaptchaService;
import com.xingyuv.captcha.util.*;
import org.slf4j.Logger;
@@ -25,6 +26,8 @@ import java.util.Base64;
import java.util.Objects;
import java.util.Properties;
+import static com.xingyuv.captcha.model.common.Const.CAPTCHA_DECRYPT_TYPE;
+
/**
* Created by raodeming on 2019/12/25.
*/
@@ -71,6 +74,8 @@ public abstract class AbstractCaptchaService implements CaptchaService {
protected static int captchaInterferenceOptions = 0;
+ private CaptchaDecryptService captchaDecryptService;
+
/**
* 判断应用是否实现了自定义缓存,没有就使用内存
*
@@ -88,13 +93,15 @@ public abstract class AbstractCaptchaService implements CaptchaService {
waterMark = config.getProperty(Const.CAPTCHA_WATER_MARK, waterMark);
slipOffset = config.getProperty(Const.CAPTCHA_SLIP_OFFSET, slipOffset);
waterMarkFontStr = config.getProperty(Const.CAPTCHA_WATER_FONT, clickWordFontStr);
- captchaAesStatus = Boolean.parseBoolean(config.getProperty(Const.CAPTCHA_AES_STATUS, "true"));
clickWordFontStr = config.getProperty(Const.CAPTCHA_FONT_TYPE, clickWordFontStr);
//clickWordFontStr = config.getProperty(Const.CAPTCHA_FONT_TYPE, "SourceHanSansCN-Normal.otf");
cacheType = config.getProperty(Const.CAPTCHA_CACHE_TYPE, cacheType);
captchaInterferenceOptions = Integer.parseInt(
config.getProperty(Const.CAPTCHA_INTERFERENCE_OPTIONS, "0"));
+ String decryptType = config.getProperty(CAPTCHA_DECRYPT_TYPE, "aes");
+ captchaAesStatus = decryptType.equalsIgnoreCase("aes");
+ captchaDecryptService = CaptchaDecryptServiceFactory.getDecryptService(decryptType);
// 部署在linux中,如果没有安装中文字段,水印和点选文字,中文无法显示,
// 通过加载resources下的font字体解决,无需在linux中安装字体
loadWaterMarkFont();
@@ -257,8 +264,8 @@ public abstract class AbstractCaptchaService implements CaptchaService {
* @return 前端坐标aes加密
* @throws Exception E
*/
- public static String decrypt(String point, String key) throws Exception {
- return AESUtil.aesDecrypt(point, key);
+ public String decrypt(String point, String key) throws Exception {
+ return captchaDecryptService.decrypt(point, key);
}
protected static int getEnOrChLength(String s) {
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java
index 32c9a833ce18013bff1768547dfb66c2ac7e3f99..905aefb1dbd9b277222d0dc072a55797a3841ec3 100644
--- a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java
@@ -99,6 +99,7 @@ public class BlockPuzzleCaptchaServiceImpl extends AbstractCaptchaService {
String pointJson = null;
try {
point = JsonUtil.parseObject(s, PointVO.class);
+ //将原有的固定 aes 解密过程改为接口解密,支持自定义实现解密流程
//aes解密
pointJson = decrypt(captchaVO.getPointJson(), point.getSecretKey());
point1 = JsonUtil.parseObject(pointJson, PointVO.class);
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaAesDecryptService.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaAesDecryptService.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ead5da736ab75915b95ce66e56f99eab0c4cc47
--- /dev/null
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaAesDecryptService.java
@@ -0,0 +1,19 @@
+package com.xingyuv.captcha.service.impl;
+
+import com.xingyuv.captcha.service.CaptchaDecryptService;
+import com.xingyuv.captcha.util.AESUtil;
+
+/**
+ * AES 解密
+ */
+public class CaptchaAesDecryptService implements CaptchaDecryptService {
+ @Override
+ public String decrypt(String point, String key) throws Exception {
+ return AESUtil.aesDecrypt(point, key);
+ }
+
+ @Override
+ public String getType() {
+ return "AES";
+ }
+}
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaBlankDecryptService.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaBlankDecryptService.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a53400de1c8f56aac151c05fb710d7f3fff80b2
--- /dev/null
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaBlankDecryptService.java
@@ -0,0 +1,18 @@
+package com.xingyuv.captcha.service.impl;
+
+import com.xingyuv.captcha.service.CaptchaDecryptService;
+
+/**
+ * 无需解密
+ */
+public class CaptchaBlankDecryptService implements CaptchaDecryptService {
+ @Override
+ public String decrypt(String point, String key) throws Exception {
+ return point;
+ }
+
+ @Override
+ public String getType() {
+ return "blank";
+ }
+}
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaDecryptServiceFactory.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaDecryptServiceFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e767e4cac615be1f93928ff03cb285f69765d22
--- /dev/null
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/service/impl/CaptchaDecryptServiceFactory.java
@@ -0,0 +1,28 @@
+package com.xingyuv.captcha.service.impl;
+
+import com.xingyuv.captcha.service.CaptchaDecryptService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+public class CaptchaDecryptServiceFactory {
+ private static final Logger log = LoggerFactory.getLogger(CaptchaDecryptServiceFactory.class);
+ private static final Map DECRYPT_SERVICE_HASH_MAP = new HashMap<>();
+
+ static {
+ ServiceLoader decryptServices = ServiceLoader.load(CaptchaDecryptService.class);
+ for (CaptchaDecryptService decryptService : decryptServices) {
+ DECRYPT_SERVICE_HASH_MAP.put(decryptService.getType().toLowerCase(Locale.ROOT), decryptService);
+ }
+ log.debug("supported-captchaDecrypt-service:{}", DECRYPT_SERVICE_HASH_MAP.keySet());
+
+ }
+
+ public static CaptchaDecryptService getDecryptService(String type) {
+ return DECRYPT_SERVICE_HASH_MAP.get(type.toLowerCase(Locale.ROOT));
+ }
+}
diff --git a/core/captcha-plus/src/main/java/com/xingyuv/captcha/util/AESUtil.java b/core/captcha-plus/src/main/java/com/xingyuv/captcha/util/AESUtil.java
index ecf695bcef6d76cbce955adbee5604eb56233506..eea71f6b4b5271330268f1cf6362d58bed90414e 100644
--- a/core/captcha-plus/src/main/java/com/xingyuv/captcha/util/AESUtil.java
+++ b/core/captcha-plus/src/main/java/com/xingyuv/captcha/util/AESUtil.java
@@ -7,11 +7,12 @@
package com.xingyuv.captcha.util;
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
+import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class AESUtil {
@@ -57,9 +58,8 @@ public class AESUtil {
*
* @param base64Code 待解码的base 64 code
* @return 解码后的byte[]
- * @throws Exception e
*/
- public static byte[] base64Decode(String base64Code) throws Exception {
+ public static byte[] base64Decode(String base64Code) {
Base64.Decoder decoder = Base64.getDecoder();
return StringUtils.isEmpty(base64Code) ? null : decoder.decode(base64Code);
}
@@ -106,7 +106,8 @@ public class AESUtil {
* @return 解密后的String
* @throws Exception e
*/
- public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
+
+ public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
@@ -125,7 +126,7 @@ public class AESUtil {
* @return 解密后的string
* @throws Exception e
*/
- public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
+ public static String aesDecrypt(String encryptStr, String decryptKey) throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {
if (StringUtils.isBlank(decryptKey)) {
return encryptStr;
}
diff --git a/core/captcha-plus/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaDecryptService b/core/captcha-plus/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaDecryptService
new file mode 100644
index 0000000000000000000000000000000000000000..d13908e205a1f1cd196b920f562f1fc15263dfbd
--- /dev/null
+++ b/core/captcha-plus/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaDecryptService
@@ -0,0 +1,2 @@
+com.xingyuv.captcha.service.impl.CaptchaAesDecryptService
+com.xingyuv.captcha.service.impl.CaptchaBlankDecryptService
\ No newline at end of file
diff --git a/core/captcha-plus/src/test/java/com/xingyuv/captcha/util/CaptchaDecryptServiceTest.java b/core/captcha-plus/src/test/java/com/xingyuv/captcha/util/CaptchaDecryptServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3869380747de51d4fa8947c9370a99860ddb4d78
--- /dev/null
+++ b/core/captcha-plus/src/test/java/com/xingyuv/captcha/util/CaptchaDecryptServiceTest.java
@@ -0,0 +1,28 @@
+package com.xingyuv.captcha.util;
+
+import com.xingyuv.captcha.service.CaptchaDecryptService;
+import com.xingyuv.captcha.service.impl.CaptchaDecryptServiceFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CaptchaDecryptServiceTest {
+ @Test
+ void testGetInstance() {
+ CaptchaDecryptService aes = CaptchaDecryptServiceFactory.getDecryptService("AES");
+ assertNotNull(aes);
+ CaptchaDecryptService blank = CaptchaDecryptServiceFactory.getDecryptService("blank");
+ assertNotNull(blank);
+ CaptchaDecryptService test = CaptchaDecryptServiceFactory.getDecryptService("test");
+ assertNotNull(test);
+ }
+
+ @Test
+ void testCustomDecrypt() throws Exception {
+ CaptchaDecryptService test = CaptchaDecryptServiceFactory.getDecryptService("test");
+ assertEquals(test.decrypt("test", null), "");
+ assertThrows(NullPointerException.class, () -> test.decrypt(null, ""));
+ assertThrows(Exception.class, () -> test.decrypt("", ""));
+ }
+
+}
diff --git a/core/captcha-plus/src/test/java/com/xingyuv/captcha/util/CaptchaTestDecryptService.java b/core/captcha-plus/src/test/java/com/xingyuv/captcha/util/CaptchaTestDecryptService.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8c499c72d67066321ca0b03345929916d98ade1
--- /dev/null
+++ b/core/captcha-plus/src/test/java/com/xingyuv/captcha/util/CaptchaTestDecryptService.java
@@ -0,0 +1,24 @@
+package com.xingyuv.captcha.util;
+
+import com.xingyuv.captcha.service.CaptchaDecryptService;
+
+import java.util.Objects;
+
+/**
+ * 测试解密
+ */
+public class CaptchaTestDecryptService implements CaptchaDecryptService {
+ @Override
+ public String decrypt(String point, String key) throws Exception {
+ Objects.requireNonNull(point);
+ if (key!=null) {
+ throw new Exception();
+ }
+ return "";
+ }
+
+ @Override
+ public String getType() {
+ return "TEST";
+ }
+}
diff --git a/core/captcha-plus/src/test/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaDecryptService b/core/captcha-plus/src/test/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaDecryptService
new file mode 100644
index 0000000000000000000000000000000000000000..e0aeb2fe007baf419d0ae8d36848249c35f28a65
--- /dev/null
+++ b/core/captcha-plus/src/test/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaDecryptService
@@ -0,0 +1,2 @@
+com.xingyuv.captcha.service.impl.CaptchaAesDecryptService
+com.xingyuv.captcha.util.CaptchaTestDecryptService
diff --git a/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/config/AjCaptchaServiceAutoConfiguration.java b/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/config/AjCaptchaServiceAutoConfiguration.java
index d6f98699e3f41e67db5b15b849f27c6e509de921..b182b992022715fb1e8d1bf59082e377487558ea 100644
--- a/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/config/AjCaptchaServiceAutoConfiguration.java
+++ b/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/config/AjCaptchaServiceAutoConfiguration.java
@@ -35,6 +35,7 @@ public class AjCaptchaServiceAutoConfiguration {
config.put(Const.ORIGINAL_PATH_PIC_CLICK, prop.getPicClick());
config.put(Const.CAPTCHA_SLIP_OFFSET, prop.getSlipOffset());
config.put(Const.CAPTCHA_AES_STATUS, String.valueOf(prop.getAesStatus()));
+ config.put(Const.CAPTCHA_DECRYPT_TYPE, prop.getDecryptType());
config.put(Const.CAPTCHA_WATER_FONT, prop.getWaterFont());
config.put(Const.CAPTCHA_CACHE_MAX_NUMBER, prop.getCacheNumber());
config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, prop.getTimingClear());
diff --git a/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/properties/AjCaptchaProperties.java b/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/properties/AjCaptchaProperties.java
index 313f4710278a43b60b029782063b7914291295d5..d4d95d89ab127b9811e6a287ab59479afac7e537 100644
--- a/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/properties/AjCaptchaProperties.java
+++ b/core/spring-boot-starter-captcha-plus/src/main/java/com/xingyuv/captcha/properties/AjCaptchaProperties.java
@@ -51,6 +51,7 @@ public class AjCaptchaProperties {
/**
* aes加密坐标开启或者禁用(true|false).
*/
+ @Deprecated
private Boolean aesStatus = true;
/**
@@ -113,6 +114,8 @@ public class AjCaptchaProperties {
*/
private int clickWordCount = 4;
+ private String decryptType = "aes";
+
public int getFontStyle() {
return fontStyle;
}
@@ -272,10 +275,12 @@ public class AjCaptchaProperties {
this.slipOffset = slipOffset;
}
+ @Deprecated
public Boolean getAesStatus() {
return aesStatus;
}
+ @Deprecated
public void setAesStatus(Boolean aesStatus) {
this.aesStatus = aesStatus;
}
@@ -312,9 +317,17 @@ public class AjCaptchaProperties {
this.timingClear = timingClear;
}
+ public String getDecryptType() {
+ return decryptType;
+ }
+
+ public void setDecryptType(String decryptType) {
+ this.decryptType = decryptType;
+ }
+
@Override
public String toString() {
- return "\nAjCaptchaProperties{" +
+ return "AjCaptchaProperties{" +
"type=" + type +
", jigsaw='" + jigsaw + '\'' +
", picClick='" + picClick + '\'' +
@@ -327,12 +340,17 @@ public class AjCaptchaProperties {
", cacheNumber='" + cacheNumber + '\'' +
", timingClear='" + timingClear + '\'' +
", cacheType=" + cacheType +
+ ", historyDataClearEnable=" + historyDataClearEnable +
", reqFrequencyLimitEnable=" + reqFrequencyLimitEnable +
", reqGetLockLimit=" + reqGetLockLimit +
", reqGetLockSeconds=" + reqGetLockSeconds +
", reqGetMinuteLimit=" + reqGetMinuteLimit +
", reqCheckMinuteLimit=" + reqCheckMinuteLimit +
", reqVerifyMinuteLimit=" + reqVerifyMinuteLimit +
+ ", fontStyle=" + fontStyle +
+ ", fontSize=" + fontSize +
+ ", clickWordCount=" + clickWordCount +
+ ", decryptType='" + decryptType + '\'' +
'}';
}
}