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; package cn.iocoder.yudao.framework.common.util.validation;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import org.springframework.util.StringUtils; 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; import java.util.regex.Pattern;
/** /**
@ -34,4 +39,11 @@ public class ValidationUtils {
&& PATTERN_XML_NCNAME.matcher(str).matches(); && 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); Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
// TODO @aquan貌似抽象一个 validation group 就好了
/** /**
* 参数校验 * 参数校验
* *

View File

@ -10,6 +10,16 @@ tenant-id: {{adminTenentId}}
"code": "1024" "code": "1024"
} }
### 请求 /login 接口 => 成功(无验证码)
POST {{baseUrl}}/system/login
Content-Type: application/json
tenant-id: {{adminTenentId}}
{
"username": "admin",
"password": "admin123"
}
### 请求 /get-permission-info 接口 => 成功 ### 请求 /get-permission-info 接口 => 成功
GET {{baseUrl}}/system/get-permission-info GET {{baseUrl}}/system/get-permission-info
Authorization: Bearer {{token}} Authorization: Bearer {{token}}

View File

@ -29,12 +29,17 @@ public class AuthLoginReqVO {
@Length(min = 4, max = 16, message = "密码长度为 4-16 位") @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password; private String password;
@ApiModelProperty(value = "验证码", required = true, example = "1024") @ApiModelProperty(value = "验证码", required = true, example = "1024", notes = "验证码开启时,需要传递")
@NotEmpty(message = "验证码不能为空") @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class)
private String code; private String code;
@ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62") @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递")
@NotEmpty(message = "唯一标识不能为空") @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class)
private String uuid; private String uuid;
/**
* 开启验证码的 Group
*/
public interface CodeEnableGroup {}
} }

View File

@ -14,11 +14,14 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class CaptchaImageRespVO { public class CaptchaImageRespVO {
@ApiModelProperty(value = "uuid", required = true, example = "1b3b7d00-83a8-4638-9e37-d67011855968", @ApiModelProperty(value = "是否开启", required = true, example = "true", notes = "如果为 false则关闭验证码功能")
notes = "通过该 uuid 作为该验证码的标识") private Boolean enable;
@ApiModelProperty(value = "uuid", example = "1b3b7d00-83a8-4638-9e37-d67011855968",
notes = "enable = true 时,非空!通过该 uuid 作为该验证码的标识")
private String uuid; private String uuid;
@ApiModelProperty(value = "图片", required = true, notes = "验证码的图片内容,使用 Base64 编码") @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码")
private String img; 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.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 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.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.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; 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.AuthLoginReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; 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.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.common.CaptchaService;
import cn.iocoder.yudao.module.system.service.logger.LoginLogService; 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.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
@ -35,6 +36,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Resource @Resource
private SocialUserService socialUserService; private SocialUserService socialUserService;
@Resource
private Validator validator;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 获取 username 对应的 AdminUserDO // 获取 username 对应的 AdminUserDO
@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Override @Override
public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { 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()); LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService {
return userSessionService.createUserSession(loginUser, userIp, userAgent); return userSessionService.createUserSession(loginUser, userIp, userAgent);
} }
private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { private void verifyCaptcha(AuthLoginReqVO reqVO) {
// 如果验证码关闭则不进行校验 // 如果验证码关闭则不进行校验
if (!captchaService.isCaptchaEnable()) { if (!captchaService.isCaptchaEnable()) {
return; return;
} }
// 校验验证码
ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
// 验证码不存在 // 验证码不存在
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
String code = captchaService.getCaptchaCode(captchaUUID); String code = captchaService.getCaptchaCode(reqVO.getUuid());
if (code == null) { 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); 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); throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
} }
// 正确所以要删除下验证码 // 正确所以要删除下验证码
captchaService.deleteCaptchaCode(captchaUUID); captchaService.deleteCaptchaCode(reqVO.getUuid());
} }
private LoginUser login0(String username, String password) { 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.framework.captcha.config.CaptchaProperties;
import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService {
@Resource @Resource
private CaptchaProperties captchaProperties; private CaptchaProperties captchaProperties;
/**
* 验证码是否开关
*
* 虽然 {@link CaptchaProperties#getEnable()} 有该属性但是 Apollo Spring Boot 下无法刷新 @ConfigurationProperties 注解
* 所以暂时只能这么处理~
*/
@Value("${yudao.captcha.enable}")
private Boolean enable;
@Resource @Resource
private CaptchaRedisDAO captchaRedisDAO; private CaptchaRedisDAO captchaRedisDAO;
@Override @Override
public CaptchaImageRespVO getCaptchaImage() { public CaptchaImageRespVO getCaptchaImage() {
if (!Boolean.TRUE.equals(enable)) {
return CaptchaImageRespVO.builder().enable(enable).build();
}
// 生成验证码 // 生成验证码
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
// 缓存到 Redis // 缓存到 Redis
String uuid = IdUtil.fastSimpleUUID(); String uuid = IdUtil.fastSimpleUUID();
captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout());
// 返回 // 返回
return CaptchaConvert.INSTANCE.convert(uuid, captcha); return CaptchaConvert.INSTANCE.convert(uuid, captcha).setEnable(enable);
} }
@Override @Override
public Boolean isCaptchaEnable() { public Boolean isCaptchaEnable() {
return captchaProperties.getEnable(); return enable;
} }
@Override @Override

View File

@ -17,7 +17,7 @@
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input> </el-input>
</el-form-item> </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"> <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" /> <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input> </el-input>
@ -61,6 +61,7 @@ export default {
data() { data() {
return { return {
codeUrl: "", codeUrl: "",
captchaEnable: true,
loginForm: { loginForm: {
username: "admin", username: "admin",
password: "admin123", password: "admin123",
@ -119,10 +120,18 @@ export default {
}, },
methods: { methods: {
getCode() { getCode() {
//
if (!this.captchaEnable) {
return;
}
//
getCodeImg().then(res => { getCodeImg().then(res => {
res = res.data; res = res.data;
this.codeUrl = "data:image/gif;base64," + res.img; this.captchaEnable = res.enable;
this.loginForm.uuid = res.uuid; if (this.captchaEnable) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
}); });
}, },
getCookie() { getCookie() {

View File

@ -37,6 +37,7 @@
* 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c) * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c)
* 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b) * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b)
* 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764) * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764)
* 【新增】在基础设施-配置管理菜单,可通过修改 `yudao.captcha.enable` 配置项,动态修改登录是否需要验证码 [commit]()
### 🐞 Bug Fixes ### 🐞 Bug Fixes