Commit ec378d75de5660d873514fa16cfabb3671b9eeb4

Authored by YunaiV
1 parent e03a1a8b

v3.7.0 参数管理支持配置验证码开关

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
1 package cn.iocoder.yudao.framework.common.util.validation; 1 package cn.iocoder.yudao.framework.common.util.validation;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
4 import org.springframework.util.StringUtils; 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 import java.util.regex.Pattern; 11 import java.util.regex.Pattern;
7 12
8 /** 13 /**
@@ -34,4 +39,11 @@ public class ValidationUtils { @@ -34,4 +39,11 @@ public class ValidationUtils {
34 && PATTERN_XML_NCNAME.matcher(str).matches(); 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
@@ -27,6 +27,7 @@ public interface PayClientConfig { @@ -27,6 +27,7 @@ public interface PayClientConfig {
27 */ 27 */
28 Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator); 28 Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
29 29
  30 + // TODO @aquan:貌似抽象一个 validation group 就好了!
30 /** 31 /**
31 * 参数校验 32 * 参数校验
32 * 33 *
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,6 +10,16 @@ tenant-id: {{adminTenentId}}
10 "code": "1024" 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 ### 请求 /get-permission-info 接口 => 成功 23 ### 请求 /get-permission-info 接口 => 成功
14 GET {{baseUrl}}/system/get-permission-info 24 GET {{baseUrl}}/system/get-permission-info
15 Authorization: Bearer {{token}} 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,12 +29,17 @@ public class AuthLoginReqVO {
29 @Length(min = 4, max = 16, message = "密码长度为 4-16 位") 29 @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
30 private String password; 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 private String code; 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 private String uuid; 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,11 +14,14 @@ import lombok.NoArgsConstructor;
14 @AllArgsConstructor 14 @AllArgsConstructor
15 public class CaptchaImageRespVO { 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 private String uuid; 22 private String uuid;
20 23
21 - @ApiModelProperty(value = "图片", required = true, notes = "验证码的图片内容,使用 Base64 编码") 24 + @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码")
22 private String img; 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,8 +4,10 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 5 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  7 +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
7 import cn.iocoder.yudao.framework.security.core.LoginUser; 8 import cn.iocoder.yudao.framework.security.core.LoginUser;
8 import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; 9 import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
  10 +import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
9 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO;
10 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; 12 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO;
11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; 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,7 +18,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
16 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 18 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
17 import cn.iocoder.yudao.module.system.service.common.CaptchaService; 19 import cn.iocoder.yudao.module.system.service.common.CaptchaService;
18 import cn.iocoder.yudao.module.system.service.logger.LoginLogService; 20 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
19 -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;  
20 import cn.iocoder.yudao.module.system.service.permission.PermissionService; 21 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
21 import cn.iocoder.yudao.module.system.service.social.SocialUserService; 22 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
22 import cn.iocoder.yudao.module.system.service.user.AdminUserService; 23 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
@@ -35,6 +36,7 @@ import org.springframework.stereotype.Service; @@ -35,6 +36,7 @@ import org.springframework.stereotype.Service;
35 import org.springframework.util.Assert; 36 import org.springframework.util.Assert;
36 37
37 import javax.annotation.Resource; 38 import javax.annotation.Resource;
  39 +import javax.validation.Validator;
38 import java.util.Objects; 40 import java.util.Objects;
39 import java.util.Set; 41 import java.util.Set;
40 42
@@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
69 @Resource 71 @Resource
70 private SocialUserService socialUserService; 72 private SocialUserService socialUserService;
71 73
  74 + @Resource
  75 + private Validator validator;
  76 +
72 @Override 77 @Override
73 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 78 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
74 // 获取 username 对应的 AdminUserDO 79 // 获取 username 对应的 AdminUserDO
@@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
96 @Override 101 @Override
97 public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { 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 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); 107 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
@@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService {
105 return userSessionService.createUserSession(loginUser, userIp, userAgent); 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 if (!captchaService.isCaptchaEnable()) { 115 if (!captchaService.isCaptchaEnable()) {
111 return; 116 return;
112 } 117 }
  118 + // 校验验证码
  119 + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
113 // 验证码不存在 120 // 验证码不存在
114 final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; 121 final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
115 - String code = captchaService.getCaptchaCode(captchaUUID); 122 + String code = captchaService.getCaptchaCode(reqVO.getUuid());
116 if (code == null) { 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 throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); 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 throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); 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 private LoginUser login0(String username, String password) { 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,6 +7,7 @@ import cn.iocoder.yudao.module.system.convert.common.CaptchaConvert;
7 import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; 7 import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties;
8 import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; 8 import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
9 import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; 9 import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO;
  10 +import org.springframework.beans.factory.annotation.Value;
10 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
11 12
12 import javax.annotation.Resource; 13 import javax.annotation.Resource;
@@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService { @@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService {
20 @Resource 21 @Resource
21 private CaptchaProperties captchaProperties; 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 @Resource 33 @Resource
24 private CaptchaRedisDAO captchaRedisDAO; 34 private CaptchaRedisDAO captchaRedisDAO;
25 35
26 @Override 36 @Override
27 public CaptchaImageRespVO getCaptchaImage() { 37 public CaptchaImageRespVO getCaptchaImage() {
  38 + if (!Boolean.TRUE.equals(enable)) {
  39 + return CaptchaImageRespVO.builder().enable(enable).build();
  40 + }
28 // 生成验证码 41 // 生成验证码
29 CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); 42 CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
30 // 缓存到 Redis 中 43 // 缓存到 Redis 中
31 String uuid = IdUtil.fastSimpleUUID(); 44 String uuid = IdUtil.fastSimpleUUID();
32 captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); 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 @Override 50 @Override
38 public Boolean isCaptchaEnable() { 51 public Boolean isCaptchaEnable() {
39 - return captchaProperties.getEnable(); 52 + return enable;
40 } 53 }
41 54
42 @Override 55 @Override
yudao-ui-admin/src/views/login.vue
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> 17 <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
18 </el-input> 18 </el-input>
19 </el-form-item> 19 </el-form-item>
20 - <el-form-item prop="code"> 20 + <el-form-item prop="code" v-if="captchaEnable">
21 <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin"> 21 <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
22 <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> 22 <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
23 </el-input> 23 </el-input>
@@ -61,6 +61,7 @@ export default { @@ -61,6 +61,7 @@ export default {
61 data() { 61 data() {
62 return { 62 return {
63 codeUrl: "", 63 codeUrl: "",
  64 + captchaEnable: true,
64 loginForm: { 65 loginForm: {
65 username: "admin", 66 username: "admin",
66 password: "admin123", 67 password: "admin123",
@@ -119,10 +120,18 @@ export default { @@ -119,10 +120,18 @@ export default {
119 }, 120 },
120 methods: { 121 methods: {
121 getCode() { 122 getCode() {
  123 + // 只有开启的状态,才加载验证码。默认开启
  124 + if (!this.captchaEnable) {
  125 + return;
  126 + }
  127 + // 请求远程,获得验证码
122 getCodeImg().then(res => { 128 getCodeImg().then(res => {
123 res = res.data; 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 getCookie() { 137 getCookie() {
更新日志.md
@@ -37,6 +37,7 @@ @@ -37,6 +37,7 @@
37 * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c) 37 * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c)
38 * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b) 38 * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b)
39 * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764) 39 * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764)
  40 +* 【新增】在基础设施-配置管理菜单,可通过修改 `yudao.captcha.enable` 配置项,动态修改登录是否需要验证码 [commit]()
40 41
41 ### 🐞 Bug Fixes 42 ### 🐞 Bug Fixes
42 43