mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-09 20:57:21 +08:00
refactor: 完善密码策略取值范围校验
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
|
||||
package top.continew.admin.system.enums;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
@@ -24,9 +27,12 @@ import lombok.RequiredArgsConstructor;
|
||||
import top.continew.admin.common.constant.RegexConstants;
|
||||
import top.continew.admin.common.constant.SysConstants;
|
||||
import top.continew.admin.system.model.entity.UserDO;
|
||||
import top.continew.admin.system.service.OptionService;
|
||||
import top.continew.admin.system.service.UserPasswordHistoryService;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 密码策略枚举
|
||||
*
|
||||
@@ -38,43 +44,89 @@ import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
@RequiredArgsConstructor
|
||||
public enum PasswordPolicyEnum {
|
||||
|
||||
/**
|
||||
* 登录密码错误锁定账号的次数
|
||||
*/
|
||||
PASSWORD_ERROR_LOCK_COUNT("登录密码错误锁定账号的次数取值范围为 %d-%d", SysConstants.NO, 10, null),
|
||||
|
||||
/**
|
||||
* 登录密码错误锁定账号的时间(min)
|
||||
*/
|
||||
PASSWORD_ERROR_LOCK_MINUTES("登录密码错误锁定账号的时间取值范围为 %d-%d 分钟", 1, 1440, null),
|
||||
|
||||
/**
|
||||
* 密码有效期(天)
|
||||
*/
|
||||
PASSWORD_EXPIRATION_DAYS("密码有效期取值范围为 %d-%d 天", SysConstants.NO, 999, null),
|
||||
|
||||
/**
|
||||
* 密码到期提前提示(天)
|
||||
*/
|
||||
PASSWORD_EXPIRATION_WARNING_DAYS("密码到期提前提示取值范围为 %d-%d 天", SysConstants.NO, 998, null) {
|
||||
@Override
|
||||
public void validateRange(int value, Map<String, String> policyMap) {
|
||||
if (CollUtil.isEmpty(policyMap)) {
|
||||
super.validateRange(value, policyMap);
|
||||
return;
|
||||
}
|
||||
Integer passwordExpirationDays = ObjUtil.defaultIfNull(Convert.toInt(policyMap.get(PASSWORD_EXPIRATION_DAYS
|
||||
.name())), SpringUtil.getBean(OptionService.class).getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
|
||||
if (passwordExpirationDays > SysConstants.NO) {
|
||||
ValidationUtils.throwIf(value >= passwordExpirationDays, "密码到期前的提示时间应小于密码有效期");
|
||||
return;
|
||||
}
|
||||
super.validateRange(value, policyMap);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 密码最小长度
|
||||
*/
|
||||
PASSWORD_MIN_LENGTH("密码最小长度为 %s 个字符", 8, 32) {
|
||||
PASSWORD_MIN_LENGTH("密码最小长度取值范围为 %d-%d", 8, 32, "密码最小长度为 %d 个字符") {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
public void validate(String password, int value, UserDO user) {
|
||||
// 最小长度校验
|
||||
ValidationUtils.throwIf(StrUtil.length(password) < policyValue, this.getDescription()
|
||||
.formatted(policyValue));
|
||||
ValidationUtils.throwIf(StrUtil.length(password) < value, this.getMsg().formatted(value));
|
||||
// 完整校验
|
||||
int passwordMaxLength = this.getMax();
|
||||
ValidationUtils.throwIf(!ReUtil.isMatch(RegexConstants.PASSWORD_TEMPLATE
|
||||
.formatted(policyValue, passwordMaxLength), password), "密码长度为 {}-{} 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字", policyValue, passwordMaxLength);
|
||||
.formatted(value, passwordMaxLength), password), "密码长度为 {}-{} 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字", value, passwordMaxLength);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 密码是否必须包含特殊字符
|
||||
*/
|
||||
PASSWORD_CONTAIN_SPECIAL_CHARACTERS("密码必须包含特殊字符", SysConstants.NO, SysConstants.YES) {
|
||||
PASSWORD_CONTAIN_SPECIAL_CHARACTERS("密码是否必须包含特殊字符取值只能为是(%d)或否(%d)", SysConstants.NO, SysConstants.YES, "密码必须包含特殊字符") {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
ValidationUtils.throwIf(policyValue == SysConstants.YES && !ReUtil
|
||||
.isMatch(RegexConstants.SPECIAL_CHARACTER, password), this.getDescription());
|
||||
public void validateRange(int value, Map<String, String> policyMap) {
|
||||
ValidationUtils.throwIf(value != SysConstants.YES && value != SysConstants.NO, this.getDescription()
|
||||
.formatted(SysConstants.YES, SysConstants.NO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(String password, int value, UserDO user) {
|
||||
ValidationUtils.throwIf(value == SysConstants.YES && !ReUtil
|
||||
.isMatch(RegexConstants.SPECIAL_CHARACTER, password), this.getMsg());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 密码是否允许包含正反序账号名
|
||||
*/
|
||||
PASSWORD_ALLOW_CONTAIN_USERNAME("密码不允许包含正反序账号名", SysConstants.NO, SysConstants.YES) {
|
||||
PASSWORD_ALLOW_CONTAIN_USERNAME("密码是否允许包含正反序账号名取值只能为是(%d)或否(%d)", SysConstants.NO, SysConstants.YES, "密码不允许包含正反序账号名") {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
if (policyValue <= SysConstants.NO) {
|
||||
public void validateRange(int value, Map<String, String> policyMap) {
|
||||
ValidationUtils.throwIf(value != SysConstants.YES && value != SysConstants.NO, this.getDescription()
|
||||
.formatted(SysConstants.YES, SysConstants.NO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(String password, int value, UserDO user) {
|
||||
if (value <= SysConstants.NO) {
|
||||
String username = user.getUsername();
|
||||
ValidationUtils.throwIf(StrUtil.containsAnyIgnoreCase(password, username, StrUtil
|
||||
.reverse(username)), this.getDescription());
|
||||
.reverse(username)), this.getMsg());
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -82,66 +134,63 @@ public enum PasswordPolicyEnum {
|
||||
/**
|
||||
* 密码重复使用规则
|
||||
*/
|
||||
PASSWORD_REUSE_POLICY("不允许使用最近 %s 次的历史密码", 3, 32) {
|
||||
PASSWORD_REUSE_POLICY("密码重复使用规则取值范围为 %d-%d", 3, 32, "不允许使用最近 %d 次的历史密码") {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
public void validate(String password, int value, UserDO user) {
|
||||
UserPasswordHistoryService userPasswordHistoryService = SpringUtil
|
||||
.getBean(UserPasswordHistoryService.class);
|
||||
ValidationUtils.throwIf(userPasswordHistoryService.isPasswordReused(user
|
||||
.getId(), password, policyValue), this.getDescription().formatted(policyValue));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 登录密码错误锁定账号的次数
|
||||
*/
|
||||
PASSWORD_ERROR_LOCK_COUNT(null, SysConstants.NO, 10) {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
// 无需此处校验
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 登录密码错误锁定账号的时间(min)
|
||||
*/
|
||||
PASSWORD_ERROR_LOCK_MINUTES(null, 1, 1440) {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
// 无需此处校验
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 密码到期提前提示(天)
|
||||
*/
|
||||
PASSWORD_EXPIRATION_WARNING_DAYS(null, SysConstants.NO, Integer.MAX_VALUE) {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
// 无需此处校验
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 密码有效期(天)
|
||||
*/
|
||||
PASSWORD_EXPIRATION_DAYS(null, SysConstants.NO, 999) {
|
||||
@Override
|
||||
public void validate(String password, int policyValue, UserDO user) {
|
||||
// 无需此处校验
|
||||
ValidationUtils.throwIf(userPasswordHistoryService.isPasswordReused(user.getId(), password, value), this
|
||||
.getMsg()
|
||||
.formatted(value));
|
||||
}
|
||||
},;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 最小值
|
||||
*/
|
||||
private final Integer min;
|
||||
|
||||
/**
|
||||
* 最大值
|
||||
*/
|
||||
private final Integer max;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
private final String msg;
|
||||
|
||||
/**
|
||||
* 策略前缀
|
||||
*/
|
||||
public static final String PREFIX = "PASSWORD_";
|
||||
|
||||
/**
|
||||
* 校验取值范围
|
||||
*
|
||||
* @param value 值
|
||||
* @param policyMap 策略集合
|
||||
*/
|
||||
public void validateRange(int value, Map<String, String> policyMap) {
|
||||
Integer minValue = this.getMin();
|
||||
Integer maxValue = this.getMax();
|
||||
ValidationUtils.throwIf(value < minValue || value > maxValue, this.getDescription()
|
||||
.formatted(minValue, maxValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验
|
||||
*
|
||||
* @param password 密码
|
||||
* @param policyValue 策略值
|
||||
* @param user 用户信息
|
||||
* @param password 密码
|
||||
* @param value 策略值
|
||||
* @param user 用户信息
|
||||
*/
|
||||
public abstract void validate(String password, int policyValue, UserDO user);
|
||||
public void validate(String password, int value, UserDO user) {
|
||||
// 无需校验
|
||||
}
|
||||
}
|
||||
|
@@ -43,9 +43,9 @@ public interface OptionService {
|
||||
/**
|
||||
* 修改参数
|
||||
*
|
||||
* @param req 参数信息
|
||||
* @param options 参数列表
|
||||
*/
|
||||
void update(List<OptionReq> req);
|
||||
void update(List<OptionReq> options);
|
||||
|
||||
/**
|
||||
* 重置参数
|
||||
|
@@ -17,10 +17,12 @@
|
||||
package top.continew.admin.system.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.continew.admin.common.constant.CacheConstants;
|
||||
import top.continew.admin.system.enums.PasswordPolicyEnum;
|
||||
import top.continew.admin.system.mapper.OptionMapper;
|
||||
import top.continew.admin.system.model.entity.OptionDO;
|
||||
import top.continew.admin.system.model.query.OptionQuery;
|
||||
@@ -31,10 +33,13 @@ import top.continew.admin.system.service.OptionService;
|
||||
import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.validate.CheckUtils;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.continew.starter.data.mybatis.plus.query.QueryWrapperHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 参数业务实现
|
||||
@@ -54,9 +59,20 @@ public class OptionServiceImpl implements OptionService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(List<OptionReq> req) {
|
||||
public void update(List<OptionReq> options) {
|
||||
Map<String, String> passwordPolicyOptionMap = options.stream()
|
||||
.filter(option -> StrUtil.startWith(option.getCode(), PasswordPolicyEnum.PREFIX))
|
||||
.collect(Collectors.toMap(OptionReq::getCode, OptionReq::getValue, (oldVal, newVal) -> oldVal));
|
||||
// 校验密码策略参数取值范围
|
||||
for (Map.Entry<String, String> passwordPolicyOptionEntry : passwordPolicyOptionMap.entrySet()) {
|
||||
String code = passwordPolicyOptionEntry.getKey();
|
||||
String value = passwordPolicyOptionEntry.getValue();
|
||||
ValidationUtils.throwIf(!NumberUtil.isNumber(value), "参数 [%s] 的值必须为数字", code);
|
||||
PasswordPolicyEnum passwordPolicy = PasswordPolicyEnum.valueOf(code);
|
||||
passwordPolicy.validateRange(Integer.parseInt(value), passwordPolicyOptionMap);
|
||||
}
|
||||
RedisUtils.deleteByPattern(CacheConstants.OPTION_KEY_PREFIX + StringConstants.ASTERISK);
|
||||
baseMapper.updateBatchById(BeanUtil.copyToList(req, OptionDO.class));
|
||||
baseMapper.updateBatchById(BeanUtil.copyToList(options, OptionDO.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -19,6 +19,7 @@ package top.continew.admin.webapi.system;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -38,6 +39,7 @@ import java.util.List;
|
||||
* @since 2023/8/26 19:38
|
||||
*/
|
||||
@Tag(name = "参数管理 API")
|
||||
@Validated
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/system/option")
|
||||
@@ -55,8 +57,8 @@ public class OptionController {
|
||||
@Operation(summary = "修改参数", description = "修改参数")
|
||||
@SaCheckPermission("system:config:update")
|
||||
@PatchMapping
|
||||
public R<Void> update(@Validated @RequestBody List<OptionReq> req) {
|
||||
baseService.update(req);
|
||||
public R<Void> update(@Valid @RequestBody List<OptionReq> options) {
|
||||
baseService.update(options);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
@@ -117,8 +117,8 @@ VALUES
|
||||
('系统LOGO(33*33)', 'SITE_LOGO', NULL, '/logo.svg', '用于显示登录页面的系统LOGO。', NULL, NULL),
|
||||
('登录密码错误锁定账号的次数', 'PASSWORD_ERROR_LOCK_COUNT', NULL, '5', '取值范围为 0-10(0 表示不锁定)。', NULL, NULL),
|
||||
('登录密码错误锁定账号的时间(min)', 'PASSWORD_ERROR_LOCK_MINUTES', NULL, '5', '取值范围为 1-1440(一天)。', NULL, NULL),
|
||||
('密码到期提前提示(天)', 'PASSWORD_EXPIRATION_WARNING_DAYS', NULL, '0', '密码到期 N 天前进行提示(0 表示不提示)。', NULL, NULL),
|
||||
('密码有效期(天)', 'PASSWORD_EXPIRATION_DAYS', NULL, '0', '取值范围为 0-999(0 表示永久有效)。', NULL, NULL),
|
||||
('密码到期提前提示(天)', 'PASSWORD_EXPIRATION_WARNING_DAYS', NULL, '0', '密码到期 N 天前进行提示(0 表示不提示)。', NULL, NULL),
|
||||
('密码重复使用规则', 'PASSWORD_REUSE_POLICY', NULL, '5', '不允许使用最近 N 次密码,取值范围为 3-32。', NULL, NULL),
|
||||
('密码最小长度', 'PASSWORD_MIN_LENGTH', NULL, '8', '取值范围为 8-32。', NULL, NULL),
|
||||
('密码是否允许包含正反序账号名', 'PASSWORD_ALLOW_CONTAIN_USERNAME', NULL, '1', '', NULL, NULL),
|
||||
|
@@ -117,8 +117,8 @@ VALUES
|
||||
('系统LOGO(33*33)', 'SITE_LOGO', NULL, '/logo.svg', '用于显示登录页面的系统LOGO。', NULL, NULL),
|
||||
('登录密码错误锁定账号的次数', 'PASSWORD_ERROR_LOCK_COUNT', NULL, '5', '取值范围为 0-10(0 表示不锁定)。', NULL, NULL),
|
||||
('登录密码错误锁定账号的时间(min)', 'PASSWORD_ERROR_LOCK_MINUTES', NULL, '5', '取值范围为 1-1440(一天)。', NULL, NULL),
|
||||
('密码到期提前提示(天)', 'PASSWORD_EXPIRATION_WARNING_DAYS', NULL, '0', '密码到期 N 天前进行提示(0 表示不提示)。', NULL, NULL),
|
||||
('密码有效期(天)', 'PASSWORD_EXPIRATION_DAYS', NULL, '0', '取值范围为 0-999(0 表示永久有效)。', NULL, NULL),
|
||||
('密码到期提前提示(天)', 'PASSWORD_EXPIRATION_WARNING_DAYS', NULL, '0', '密码到期 N 天前进行提示(0 表示不提示)。', NULL, NULL),
|
||||
('密码重复使用规则', 'PASSWORD_REUSE_POLICY', NULL, '5', '不允许使用最近 N 次密码,取值范围为 3-32。', NULL, NULL),
|
||||
('密码最小长度', 'PASSWORD_MIN_LENGTH', NULL, '8', '取值范围为 8-32。', NULL, NULL),
|
||||
('密码是否允许包含正反序账号名', 'PASSWORD_ALLOW_CONTAIN_USERNAME', NULL, '1', '', NULL, NULL),
|
||||
|
Reference in New Issue
Block a user