mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-26 09:11:52 +08:00
v3.7.0 参数管理支持配置验证码开关
This commit is contained in:
parent
e03a1a8bb3
commit
ec378d75de
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ public interface PayClientConfig {
|
|||||||
*/
|
*/
|
||||||
Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
|
Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
|
||||||
|
|
||||||
|
// TODO @aquan:貌似抽象一个 validation group 就好了!
|
||||||
/**
|
/**
|
||||||
* 参数校验
|
* 参数校验
|
||||||
*
|
*
|
||||||
|
@ -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}}
|
||||||
|
@ -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 {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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() {
|
||||||
|
1
更新日志.md
1
更新日志.md
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user