diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java index 6b4cd4d03..9ff5b0583 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java @@ -1,8 +1,13 @@ package cn.iocoder.yudao.framework.common.util.validation; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import org.springframework.util.StringUtils; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import java.util.Set; import java.util.regex.Pattern; /** @@ -34,4 +39,11 @@ public class ValidationUtils { && PATTERN_XML_NCNAME.matcher(str).matches(); } + public static void validate(Validator validator, Object reqVO, Class... groups) { + Set> constraintViolations = validator.validate(reqVO, groups); + if (CollUtil.isNotEmpty(constraintViolations)) { + throw new ConstraintViolationException(constraintViolations); + } + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java index f14893c4e..bf709e497 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java @@ -27,6 +27,7 @@ public interface PayClientConfig { */ Set> verifyParam(Validator validator); + // TODO @aquan:貌似抽象一个 validation group 就好了! /** * 参数校验 * diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http index 25124ca33..e6c70f9df 100644 --- a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http @@ -10,6 +10,16 @@ tenant-id: {{adminTenentId}} "code": "1024" } +### 请求 /login 接口 => 成功(无验证码) +POST {{baseUrl}}/system/login +Content-Type: application/json +tenant-id: {{adminTenentId}} + +{ + "username": "admin", + "password": "admin123" +} + ### 请求 /get-permission-info 接口 => 成功 GET {{baseUrl}}/system/get-permission-info Authorization: Bearer {{token}} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java index 8e9eb4ed2..013c0b2e7 100644 --- a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java @@ -29,12 +29,17 @@ public class AuthLoginReqVO { @Length(min = 4, max = 16, message = "密码长度为 4-16 位") private String password; - @ApiModelProperty(value = "验证码", required = true, example = "1024") - @NotEmpty(message = "验证码不能为空") + @ApiModelProperty(value = "验证码", required = true, example = "1024", notes = "验证码开启时,需要传递") + @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) private String code; - @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62") - @NotEmpty(message = "唯一标识不能为空") + @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递") + @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class) private String uuid; + /** + * 开启验证码的 Group + */ + public interface CodeEnableGroup {} + } diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java index cef5d0845..382fafcb5 100644 --- a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java @@ -14,11 +14,14 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class CaptchaImageRespVO { - @ApiModelProperty(value = "uuid", required = true, example = "1b3b7d00-83a8-4638-9e37-d67011855968", - notes = "通过该 uuid 作为该验证码的标识") + @ApiModelProperty(value = "是否开启", required = true, example = "true", notes = "如果为 false,则关闭验证码功能") + private Boolean enable; + + @ApiModelProperty(value = "uuid", example = "1b3b7d00-83a8-4638-9e37-d67011855968", + notes = "enable = true 时,非空!通过该 uuid 作为该验证码的标识") private String uuid; - @ApiModelProperty(value = "图片", required = true, notes = "验证码的图片内容,使用 Base64 编码") + @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码") private String img; } diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 7bdb58f3a..38018c460 100644 --- a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -4,8 +4,10 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; +import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; @@ -16,7 +18,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; @@ -35,6 +36,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.Assert; import javax.annotation.Resource; +import javax.validation.Validator; import java.util.Objects; import java.util.Set; @@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private SocialUserService socialUserService; + @Resource + private Validator validator; + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 获取 username 对应的 AdminUserDO @@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Override public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { // 判断验证码是否正确 - this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); + this.verifyCaptcha(reqVO); // 使用账号密码,进行登录 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); @@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService { return userSessionService.createUserSession(loginUser, userIp, userAgent); } - private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { + private void verifyCaptcha(AuthLoginReqVO reqVO) { // 如果验证码关闭,则不进行校验 if (!captchaService.isCaptchaEnable()) { return; } + // 校验验证码 + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); // 验证码不存在 final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; - String code = captchaService.getCaptchaCode(captchaUUID); + String code = captchaService.getCaptchaCode(reqVO.getUuid()); if (code == null) { // 创建登录失败日志(验证码不存在) - this.createLoginLog(username, logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); + this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); } // 验证码不正确 - if (!code.equals(captchaCode)) { + if (!code.equals(reqVO.getCode())) { // 创建登录失败日志(验证码不正确) - this.createLoginLog(username, logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); + this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); } // 正确,所以要删除下验证码 - captchaService.deleteCaptchaCode(captchaUUID); + captchaService.deleteCaptchaCode(reqVO.getUuid()); } private LoginUser login0(String username, String password) { diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java index 1c1a38875..f52f0ba3b 100644 --- a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.convert.common.CaptchaConvert; import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService { @Resource private CaptchaProperties captchaProperties; + /** + * 验证码是否开关 + * + * 虽然 {@link CaptchaProperties#getEnable()} 有该属性,但是 Apollo 在 Spring Boot 下无法刷新 @ConfigurationProperties 注解, + * 所以暂时只能这么处理~ + */ + @Value("${yudao.captcha.enable}") + private Boolean enable; + @Resource private CaptchaRedisDAO captchaRedisDAO; @Override public CaptchaImageRespVO getCaptchaImage() { + if (!Boolean.TRUE.equals(enable)) { + return CaptchaImageRespVO.builder().enable(enable).build(); + } // 生成验证码 CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); // 缓存到 Redis 中 String uuid = IdUtil.fastSimpleUUID(); captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); // 返回 - return CaptchaConvert.INSTANCE.convert(uuid, captcha); + return CaptchaConvert.INSTANCE.convert(uuid, captcha).setEnable(enable); } @Override public Boolean isCaptchaEnable() { - return captchaProperties.getEnable(); + return enable; } @Override diff --git a/yudao-ui-admin/src/views/login.vue b/yudao-ui-admin/src/views/login.vue index aeda01450..b1497a402 100644 --- a/yudao-ui-admin/src/views/login.vue +++ b/yudao-ui-admin/src/views/login.vue @@ -17,7 +17,7 @@ - + @@ -61,6 +61,7 @@ export default { data() { return { codeUrl: "", + captchaEnable: true, loginForm: { username: "admin", password: "admin123", @@ -119,10 +120,18 @@ export default { }, methods: { getCode() { + // 只有开启的状态,才加载验证码。默认开启 + if (!this.captchaEnable) { + return; + } + // 请求远程,获得验证码 getCodeImg().then(res => { res = res.data; - this.codeUrl = "data:image/gif;base64," + res.img; - this.loginForm.uuid = res.uuid; + this.captchaEnable = res.enable; + if (this.captchaEnable) { + this.codeUrl = "data:image/gif;base64," + res.img; + this.loginForm.uuid = res.uuid; + } }); }, getCookie() { diff --git a/更新日志.md b/更新日志.md index 0be8cfc94..d0c915b83 100644 --- a/更新日志.md +++ b/更新日志.md @@ -37,6 +37,7 @@ * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c) * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b) * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764) +* 【新增】在基础设施-配置管理菜单,可通过修改 `yudao.captcha.enable` 配置项,动态修改登录是否需要验证码 [commit]() ### 🐞 Bug Fixes