Commit ec378d75de5660d873514fa16cfabb3671b9eeb4
1 parent
e03a1a8b
v3.7.0 参数管理支持配置验证码开关
Showing
9 changed files
with
81 additions
and
20 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
| 1 | 1 | package cn.iocoder.yudao.framework.common.util.validation; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | 5 | import org.springframework.util.StringUtils; |
| 5 | 6 | |
| 7 | +import javax.validation.ConstraintViolation; | |
| 8 | +import javax.validation.ConstraintViolationException; | |
| 9 | +import javax.validation.Validator; | |
| 10 | +import java.util.Set; | |
| 6 | 11 | import java.util.regex.Pattern; |
| 7 | 12 | |
| 8 | 13 | /** |
| ... | ... | @@ -34,4 +39,11 @@ public class ValidationUtils { |
| 34 | 39 | && PATTERN_XML_NCNAME.matcher(str).matches(); |
| 35 | 40 | } |
| 36 | 41 | |
| 42 | + public static void validate(Validator validator, Object reqVO, Class<?>... groups) { | |
| 43 | + Set<ConstraintViolation<Object>> constraintViolations = validator.validate(reqVO, groups); | |
| 44 | + if (CollUtil.isNotEmpty(constraintViolations)) { | |
| 45 | + throw new ConstraintViolationException(constraintViolations); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + | |
| 37 | 49 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java
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}} |
| 10 | 10 | "code": "1024" |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | +### 请求 /login 接口 => 成功(无验证码) | |
| 14 | +POST {{baseUrl}}/system/login | |
| 15 | +Content-Type: application/json | |
| 16 | +tenant-id: {{adminTenentId}} | |
| 17 | + | |
| 18 | +{ | |
| 19 | + "username": "admin", | |
| 20 | + "password": "admin123" | |
| 21 | +} | |
| 22 | + | |
| 13 | 23 | ### 请求 /get-permission-info 接口 => 成功 |
| 14 | 24 | GET {{baseUrl}}/system/get-permission-info |
| 15 | 25 | Authorization: Bearer {{token}} | ... | ... |
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 { |
| 29 | 29 | @Length(min = 4, max = 16, message = "密码长度为 4-16 位") |
| 30 | 30 | private String password; |
| 31 | 31 | |
| 32 | - @ApiModelProperty(value = "验证码", required = true, example = "1024") | |
| 33 | - @NotEmpty(message = "验证码不能为空") | |
| 32 | + @ApiModelProperty(value = "验证码", required = true, example = "1024", notes = "验证码开启时,需要传递") | |
| 33 | + @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) | |
| 34 | 34 | private String code; |
| 35 | 35 | |
| 36 | - @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62") | |
| 37 | - @NotEmpty(message = "唯一标识不能为空") | |
| 36 | + @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递") | |
| 37 | + @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class) | |
| 38 | 38 | private String uuid; |
| 39 | 39 | |
| 40 | + /** | |
| 41 | + * 开启验证码的 Group | |
| 42 | + */ | |
| 43 | + public interface CodeEnableGroup {} | |
| 44 | + | |
| 40 | 45 | } | ... | ... |
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; |
| 14 | 14 | @AllArgsConstructor |
| 15 | 15 | public class CaptchaImageRespVO { |
| 16 | 16 | |
| 17 | - @ApiModelProperty(value = "uuid", required = true, example = "1b3b7d00-83a8-4638-9e37-d67011855968", | |
| 18 | - notes = "通过该 uuid 作为该验证码的标识") | |
| 17 | + @ApiModelProperty(value = "是否开启", required = true, example = "true", notes = "如果为 false,则关闭验证码功能") | |
| 18 | + private Boolean enable; | |
| 19 | + | |
| 20 | + @ApiModelProperty(value = "uuid", example = "1b3b7d00-83a8-4638-9e37-d67011855968", | |
| 21 | + notes = "enable = true 时,非空!通过该 uuid 作为该验证码的标识") | |
| 19 | 22 | private String uuid; |
| 20 | 23 | |
| 21 | - @ApiModelProperty(value = "图片", required = true, notes = "验证码的图片内容,使用 Base64 编码") | |
| 24 | + @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码") | |
| 22 | 25 | private String img; |
| 23 | 26 | |
| 24 | 27 | } | ... | ... |
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; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| 7 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | |
| 7 | 8 | import cn.iocoder.yudao.framework.security.core.LoginUser; |
| 8 | 9 | import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; |
| 10 | +import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; | |
| 9 | 11 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; |
| 10 | 12 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; |
| 11 | 13 | 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; |
| 16 | 18 | import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; |
| 17 | 19 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; |
| 18 | 20 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; |
| 19 | -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; | |
| 20 | 21 | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| 21 | 22 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; |
| 22 | 23 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| ... | ... | @@ -35,6 +36,7 @@ import org.springframework.stereotype.Service; |
| 35 | 36 | import org.springframework.util.Assert; |
| 36 | 37 | |
| 37 | 38 | import javax.annotation.Resource; |
| 39 | +import javax.validation.Validator; | |
| 38 | 40 | import java.util.Objects; |
| 39 | 41 | import java.util.Set; |
| 40 | 42 | |
| ... | ... | @@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { |
| 69 | 71 | @Resource |
| 70 | 72 | private SocialUserService socialUserService; |
| 71 | 73 | |
| 74 | + @Resource | |
| 75 | + private Validator validator; | |
| 76 | + | |
| 72 | 77 | @Override |
| 73 | 78 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
| 74 | 79 | // 获取 username 对应的 AdminUserDO |
| ... | ... | @@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { |
| 96 | 101 | @Override |
| 97 | 102 | public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { |
| 98 | 103 | // 判断验证码是否正确 |
| 99 | - this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); | |
| 104 | + this.verifyCaptcha(reqVO); | |
| 100 | 105 | |
| 101 | 106 | // 使用账号密码,进行登录 |
| 102 | 107 | LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); |
| ... | ... | @@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService { |
| 105 | 110 | return userSessionService.createUserSession(loginUser, userIp, userAgent); |
| 106 | 111 | } |
| 107 | 112 | |
| 108 | - private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { | |
| 113 | + private void verifyCaptcha(AuthLoginReqVO reqVO) { | |
| 109 | 114 | // 如果验证码关闭,则不进行校验 |
| 110 | 115 | if (!captchaService.isCaptchaEnable()) { |
| 111 | 116 | return; |
| 112 | 117 | } |
| 118 | + // 校验验证码 | |
| 119 | + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); | |
| 113 | 120 | // 验证码不存在 |
| 114 | 121 | final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; |
| 115 | - String code = captchaService.getCaptchaCode(captchaUUID); | |
| 122 | + String code = captchaService.getCaptchaCode(reqVO.getUuid()); | |
| 116 | 123 | if (code == null) { |
| 117 | 124 | // 创建登录失败日志(验证码不存在) |
| 118 | - this.createLoginLog(username, logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); | |
| 125 | + this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); | |
| 119 | 126 | throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); |
| 120 | 127 | } |
| 121 | 128 | // 验证码不正确 |
| 122 | - if (!code.equals(captchaCode)) { | |
| 129 | + if (!code.equals(reqVO.getCode())) { | |
| 123 | 130 | // 创建登录失败日志(验证码不正确) |
| 124 | - this.createLoginLog(username, logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); | |
| 131 | + this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); | |
| 125 | 132 | throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); |
| 126 | 133 | } |
| 127 | 134 | // 正确,所以要删除下验证码 |
| 128 | - captchaService.deleteCaptchaCode(captchaUUID); | |
| 135 | + captchaService.deleteCaptchaCode(reqVO.getUuid()); | |
| 129 | 136 | } |
| 130 | 137 | |
| 131 | 138 | private LoginUser login0(String username, String password) { | ... | ... |
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; |
| 7 | 7 | import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; |
| 8 | 8 | import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; |
| 9 | 9 | import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; |
| 10 | +import org.springframework.beans.factory.annotation.Value; | |
| 10 | 11 | import org.springframework.stereotype.Service; |
| 11 | 12 | |
| 12 | 13 | import javax.annotation.Resource; |
| ... | ... | @@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService { |
| 20 | 21 | @Resource |
| 21 | 22 | private CaptchaProperties captchaProperties; |
| 22 | 23 | |
| 24 | + /** | |
| 25 | + * 验证码是否开关 | |
| 26 | + * | |
| 27 | + * 虽然 {@link CaptchaProperties#getEnable()} 有该属性,但是 Apollo 在 Spring Boot 下无法刷新 @ConfigurationProperties 注解, | |
| 28 | + * 所以暂时只能这么处理~ | |
| 29 | + */ | |
| 30 | + @Value("${yudao.captcha.enable}") | |
| 31 | + private Boolean enable; | |
| 32 | + | |
| 23 | 33 | @Resource |
| 24 | 34 | private CaptchaRedisDAO captchaRedisDAO; |
| 25 | 35 | |
| 26 | 36 | @Override |
| 27 | 37 | public CaptchaImageRespVO getCaptchaImage() { |
| 38 | + if (!Boolean.TRUE.equals(enable)) { | |
| 39 | + return CaptchaImageRespVO.builder().enable(enable).build(); | |
| 40 | + } | |
| 28 | 41 | // 生成验证码 |
| 29 | 42 | CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); |
| 30 | 43 | // 缓存到 Redis 中 |
| 31 | 44 | String uuid = IdUtil.fastSimpleUUID(); |
| 32 | 45 | captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); |
| 33 | 46 | // 返回 |
| 34 | - return CaptchaConvert.INSTANCE.convert(uuid, captcha); | |
| 47 | + return CaptchaConvert.INSTANCE.convert(uuid, captcha).setEnable(enable); | |
| 35 | 48 | } |
| 36 | 49 | |
| 37 | 50 | @Override |
| 38 | 51 | public Boolean isCaptchaEnable() { |
| 39 | - return captchaProperties.getEnable(); | |
| 52 | + return enable; | |
| 40 | 53 | } |
| 41 | 54 | |
| 42 | 55 | @Override | ... | ... |
yudao-ui-admin/src/views/login.vue
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> |
| 18 | 18 | </el-input> |
| 19 | 19 | </el-form-item> |
| 20 | - <el-form-item prop="code"> | |
| 20 | + <el-form-item prop="code" v-if="captchaEnable"> | |
| 21 | 21 | <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin"> |
| 22 | 22 | <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> |
| 23 | 23 | </el-input> |
| ... | ... | @@ -61,6 +61,7 @@ export default { |
| 61 | 61 | data() { |
| 62 | 62 | return { |
| 63 | 63 | codeUrl: "", |
| 64 | + captchaEnable: true, | |
| 64 | 65 | loginForm: { |
| 65 | 66 | username: "admin", |
| 66 | 67 | password: "admin123", |
| ... | ... | @@ -119,10 +120,18 @@ export default { |
| 119 | 120 | }, |
| 120 | 121 | methods: { |
| 121 | 122 | getCode() { |
| 123 | + // 只有开启的状态,才加载验证码。默认开启 | |
| 124 | + if (!this.captchaEnable) { | |
| 125 | + return; | |
| 126 | + } | |
| 127 | + // 请求远程,获得验证码 | |
| 122 | 128 | getCodeImg().then(res => { |
| 123 | 129 | res = res.data; |
| 124 | - this.codeUrl = "data:image/gif;base64," + res.img; | |
| 125 | - this.loginForm.uuid = res.uuid; | |
| 130 | + this.captchaEnable = res.enable; | |
| 131 | + if (this.captchaEnable) { | |
| 132 | + this.codeUrl = "data:image/gif;base64," + res.img; | |
| 133 | + this.loginForm.uuid = res.uuid; | |
| 134 | + } | |
| 126 | 135 | }); |
| 127 | 136 | }, |
| 128 | 137 | getCookie() { | ... | ... |
更新日志.md
| ... | ... | @@ -37,6 +37,7 @@ |
| 37 | 37 | * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c) |
| 38 | 38 | * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b) |
| 39 | 39 | * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764) |
| 40 | +* 【新增】在基础设施-配置管理菜单,可通过修改 `yudao.captcha.enable` 配置项,动态修改登录是否需要验证码 [commit]() | |
| 40 | 41 | |
| 41 | 42 | ### 🐞 Bug Fixes |
| 42 | 43 | ... | ... |