diff --git a/continew-admin-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java b/continew-admin-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java index 05237f2c..eeda8e72 100644 --- a/continew-admin-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java +++ b/continew-admin-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java @@ -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 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 @@ @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 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 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 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 void validate(String password, int policyValue, UserDO user) { /** * 密码重复使用规则 */ - 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 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) { + // 无需校验 + } } diff --git a/continew-admin-system/src/main/java/top/continew/admin/system/service/OptionService.java b/continew-admin-system/src/main/java/top/continew/admin/system/service/OptionService.java index a0aec51d..cae28469 100644 --- a/continew-admin-system/src/main/java/top/continew/admin/system/service/OptionService.java +++ b/continew-admin-system/src/main/java/top/continew/admin/system/service/OptionService.java @@ -43,9 +43,9 @@ public interface OptionService { /** * 修改参数 * - * @param req 参数信息 + * @param options 参数列表 */ - void update(List req); + void update(List options); /** * 重置参数 diff --git a/continew-admin-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java b/continew-admin-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java index fef23e5d..679727d0 100644 --- a/continew-admin-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java +++ b/continew-admin-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java @@ -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.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 List list(OptionQuery query) { } @Override - public void update(List req) { + public void update(List options) { + Map passwordPolicyOptionMap = options.stream() + .filter(option -> StrUtil.startWith(option.getCode(), PasswordPolicyEnum.PREFIX)) + .collect(Collectors.toMap(OptionReq::getCode, OptionReq::getValue, (oldVal, newVal) -> oldVal)); + // 校验密码策略参数取值范围 + for (Map.Entry 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 diff --git a/continew-admin-webapi/src/main/java/top/continew/admin/webapi/system/OptionController.java b/continew-admin-webapi/src/main/java/top/continew/admin/webapi/system/OptionController.java index b549797f..23623d5c 100644 --- a/continew-admin-webapi/src/main/java/top/continew/admin/webapi/system/OptionController.java +++ b/continew-admin-webapi/src/main/java/top/continew/admin/webapi/system/OptionController.java @@ -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.*; @@ -38,6 +39,7 @@ * @since 2023/8/26 19:38 */ @Tag(name = "参数管理 API") +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/system/option") @@ -55,8 +57,8 @@ public R> list(@Validated OptionQuery query) { @Operation(summary = "修改参数", description = "修改参数") @SaCheckPermission("system:config:update") @PatchMapping - public R update(@Validated @RequestBody List req) { - baseService.update(req); + public R update(@Valid @RequestBody List options) { + baseService.update(options); return R.ok(); } diff --git a/continew-admin-webapi/src/main/resources/db/changelog/mysql/continew-admin_data.sql b/continew-admin-webapi/src/main/resources/db/changelog/mysql/continew-admin_data.sql index 04f8706c..b5c0718c 100644 --- a/continew-admin-webapi/src/main/resources/db/changelog/mysql/continew-admin_data.sql +++ b/continew-admin-webapi/src/main/resources/db/changelog/mysql/continew-admin_data.sql @@ -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), diff --git a/continew-admin-webapi/src/main/resources/db/changelog/postgresql/continew-admin_data.sql b/continew-admin-webapi/src/main/resources/db/changelog/postgresql/continew-admin_data.sql index 1e576463..37b1670c 100644 --- a/continew-admin-webapi/src/main/resources/db/changelog/postgresql/continew-admin_data.sql +++ b/continew-admin-webapi/src/main/resources/db/changelog/postgresql/continew-admin_data.sql @@ -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),