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

This commit is contained in:
YunaiV 2022-02-17 00:20:08 +08:00
parent e03a1a8bb3
commit ec378d75de
9 changed files with 81 additions and 20 deletions

View File

@ -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<ConstraintViolation<Object>> constraintViolations = validator.validate(reqVO, groups);
if (CollUtil.isNotEmpty(constraintViolations)) {
throw new ConstraintViolationException(constraintViolations);
}
}
}

View File

@ -27,6 +27,7 @@ public interface PayClientConfig {
*/
Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
// TODO @aquan貌似抽象一个 validation group 就好了
/**
* 参数校验
*

View File

@ -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}}

View File

@ -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 {}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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

View File

@ -17,7 +17,7 @@
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-form-item prop="code" v-if="captchaEnable">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
@ -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() {

View File

@ -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