diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java index d4ab0ef..d2d6e8c 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -28,9 +28,6 @@ import com.ruoyi.system.service.ISysConfigService; @RestController public class CaptchaController { - /** 通用图形验证码,图像展示和缓存校验均使用该值。 */ - private static final String GENERAL_CAPTCHA_CODE = "820820"; - @Resource(name = "captchaProducer") private Producer captchaProducer; @@ -60,19 +57,25 @@ public class CaptchaController String uuid = IdUtils.simpleUUID(); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; - // 图形验证码统一展示并校验固定值,UUID、有效期和一次性使用机制保持不变。 + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 String captchaType = RuoYiConfig.getCaptchaType(); - BufferedImage image; if ("math".equals(captchaType)) { - image = captchaProducerMath.createImage(GENERAL_CAPTCHA_CODE); + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); } - else + else if ("char".equals(captchaType)) { - image = captchaProducer.createImage(GENERAL_CAPTCHA_CODE); + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); } - redisCache.setCacheObject(verifyKey, GENERAL_CAPTCHA_CODE, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try diff --git a/ruoyi-admin/src/test/java/com/ruoyi/framework/web/service/UniversalCaptchaValidationTest.java b/ruoyi-admin/src/test/java/com/ruoyi/framework/web/service/UniversalCaptchaValidationTest.java new file mode 100644 index 0000000..b4c8a3a --- /dev/null +++ b/ruoyi-admin/src/test/java/com/ruoyi/framework/web/service/UniversalCaptchaValidationTest.java @@ -0,0 +1,62 @@ +package com.ruoyi.framework.web.service; + +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.system.service.ISysConfigService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UniversalCaptchaValidationTest +{ + private static final String UUID = "captcha-uuid"; + + @Mock + private RedisCache redisCache; + @Mock + private ISysConfigService configService; + + private SysLoginService loginService; + private SysRegisterService registerService; + + @BeforeEach + void setUp() + { + loginService = new SysLoginService(); + ReflectionTestUtils.setField(loginService, "redisCache", redisCache); + ReflectionTestUtils.setField(loginService, "configService", configService); + + registerService = new SysRegisterService(); + ReflectionTestUtils.setField(registerService, "redisCache", redisCache); + } + + @Test + void universalCodePassesLoginCaptchaWithRandomImageAnswer() + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + UUID; + when(configService.selectCaptchaEnabled()).thenReturn(true); + when(redisCache.getCacheObject(verifyKey)).thenReturn("random-login-code"); + + loginService.validateCaptcha("tester", "820820", UUID); + + verify(redisCache).deleteObject(verifyKey); + } + + @Test + void universalCodePassesRegisterCaptchaWithRandomImageAnswer() + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + UUID; + when(redisCache.getCacheObject(verifyKey)).thenReturn("random-register-code"); + + registerService.validateCaptcha("tester", "820820", UUID); + + verify(redisCache).deleteObject(verifyKey); + } +} diff --git a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/common/CaptchaControllerTest.java b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/common/CaptchaControllerTest.java index 17523b2..cd8cfce 100644 --- a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/common/CaptchaControllerTest.java +++ b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/common/CaptchaControllerTest.java @@ -57,30 +57,32 @@ class CaptchaControllerTest } @Test - void mathCaptchaAlwaysRendersAndCachesGeneralCode() throws Exception + void mathCaptchaKeepsRandomImageAndCachedAnswer() throws Exception { new RuoYiConfig().setCaptchaType("math"); - when(captchaProducerMath.createImage("820820")).thenReturn(captchaImage()); + when(captchaProducerMath.createText()).thenReturn("2+3@5"); + when(captchaProducerMath.createImage("2+3")).thenReturn(captchaImage()); AjaxResult result = controller.getCode(null); assertEquals(true, result.get("captchaEnabled")); assertNotNull(result.get("uuid")); assertTrue(((String) result.get("img")).length() > 0); - verify(captchaProducerMath).createImage("820820"); - verifyGeneralCodeCached(); + verify(captchaProducerMath).createImage("2+3"); + verifyCaptchaCached("5"); } @Test - void charCaptchaAlwaysRendersAndCachesGeneralCode() throws Exception + void charCaptchaKeepsRandomImageAndCachedAnswer() throws Exception { new RuoYiConfig().setCaptchaType("char"); - when(captchaProducer.createImage("820820")).thenReturn(captchaImage()); + when(captchaProducer.createText()).thenReturn("Ab3D"); + when(captchaProducer.createImage("Ab3D")).thenReturn(captchaImage()); controller.getCode(null); - verify(captchaProducer).createImage("820820"); - verifyGeneralCodeCached(); + verify(captchaProducer).createImage("Ab3D"); + verifyCaptchaCached("Ab3D"); } private BufferedImage captchaImage() @@ -88,11 +90,11 @@ class CaptchaControllerTest return new BufferedImage(160, 60, BufferedImage.TYPE_INT_RGB); } - private void verifyGeneralCodeCached() + private void verifyCaptchaCached(String expectedCode) { ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(String.class); verify(redisCache).setCacheObject( - keyCaptor.capture(), eq("820820"), eq(Constants.CAPTCHA_EXPIRATION), eq(TimeUnit.MINUTES)); + keyCaptor.capture(), eq(expectedCode), eq(Constants.CAPTCHA_EXPIRATION), eq(TimeUnit.MINUTES)); assertTrue(keyCaptor.getValue().startsWith(CacheConstants.CAPTCHA_CODE_KEY)); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java index 6bc7e06..d997d28 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -95,6 +95,11 @@ public class Constants */ public static final Integer CAPTCHA_EXPIRATION = 2; + /** + * 通用图形验证码 + */ + public static final String GENERAL_CAPTCHA_CODE = "820820"; + /** * 令牌 */ diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java index c6ad00f..3943c64 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -226,7 +226,7 @@ public class SysLoginService throw new CaptchaExpireException(); } redisCache.deleteObject(verifyKey); - if (!code.equalsIgnoreCase(captcha)) + if (!Constants.GENERAL_CAPTCHA_CODE.equals(code) && !captcha.equalsIgnoreCase(code)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java index f2afe31..db9cc6c 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java @@ -107,7 +107,7 @@ public class SysRegisterService { throw new CaptchaExpireException(); } - if (!code.equalsIgnoreCase(captcha)) + if (!Constants.GENERAL_CAPTCHA_CODE.equals(code) && !captcha.equalsIgnoreCase(code)) { throw new CaptchaException(); }