Skip to content

Commit

Permalink
refactor: 完善密码策略取值范围校验
Browse files Browse the repository at this point in the history
  • Loading branch information
Charles7c committed May 18, 2024
1 parent 857a1c9 commit 5f5fee6
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,9 +27,12 @@
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;

/**
* 密码策略枚举
*
Expand All @@ -38,110 +44,153 @@
@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());
}
}
},

/**
* 密码重复使用规则
*/
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));
ValidationUtils.throwIf(userPasswordHistoryService.isPasswordReused(user.getId(), password, value), this
.getMsg()
.formatted(value));
}
},
},;

/**
* 登录密码错误锁定账号的次数
* 描述
*/
PASSWORD_ERROR_LOCK_COUNT(null, SysConstants.NO, 10) {
@Override
public void validate(String password, int policyValue, UserDO user) {
// 无需此处校验
}
},
private final String description;

/**
* 登录密码错误锁定账号的时间(min)
* 最小值
*/
PASSWORD_ERROR_LOCK_MINUTES(null, 1, 1440) {
@Override
public void validate(String password, int policyValue, UserDO user) {
// 无需此处校验
}
},
private final Integer min;

/**
* 密码到期提前提示(天)
* 最大值
*/
PASSWORD_EXPIRATION_WARNING_DAYS(null, SysConstants.NO, Integer.MAX_VALUE) {
@Override
public void validate(String password, int policyValue, UserDO user) {
// 无需此处校验
}
},
private final Integer max;

/**
* 密码有效期(天)
* 提示信息
*/
PASSWORD_EXPIRATION_DAYS(null, SysConstants.NO, 999) {
@Override
public void validate(String password, int policyValue, UserDO user) {
// 无需此处校验
}
},;
private final String msg;

private final String description;
private final Integer min;
private final Integer max;
/**
* 策略前缀
*/
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) {
// 无需校验
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public interface OptionService {
/**
* 修改参数
*
* @param req 参数信息
* @param options 参数列表
*/
void update(List<OptionReq> req);
void update(List<OptionReq> options);

/**
* 重置参数
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,10 +33,13 @@
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;

/**
* 参数业务实现
Expand All @@ -54,9 +59,20 @@ public List<OptionResp> list(OptionQuery query) {
}

@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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.*;
Expand All @@ -38,6 +39,7 @@
* @since 2023/8/26 19:38
*/
@Tag(name = "参数管理 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/option")
Expand All @@ -55,8 +57,8 @@ public R<List<OptionResp>> list(@Validated OptionQuery query) {
@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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 5f5fee6

Please sign in to comment.