diff --git a/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java b/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java index 3883e656..38674041 100644 --- a/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java +++ b/backend/src/main/java/com/yfd/platform/config/SecurityConfig.java @@ -77,6 +77,7 @@ public class SecurityConfig { // .requestMatchers("/threedroamb/**").permitAll() // .requestMatchers("/overview/**").permitAll() .requestMatchers("/wt/**").permitAll() + .requestMatchers("/system/acctPasswordPolicy/**").permitAll() .requestMatchers("/fb/**").permitAll() // .requestMatchers("/base/**").permitAll() // .requestMatchers("/zq/**").permitAll() diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SysAcctPasswordPolicyController.java b/backend/src/main/java/com/yfd/platform/system/controller/SysAcctPasswordPolicyController.java new file mode 100644 index 00000000..4c7bc27f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/controller/SysAcctPasswordPolicyController.java @@ -0,0 +1,139 @@ +package com.yfd.platform.system.controller; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.common.DataSourceRequest; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.system.domain.SysAcctPasswordPolicy; +import com.yfd.platform.system.service.ISysAcctPasswordPolicyService; +import com.yfd.platform.system.service.IUserService; +import com.yfd.platform.utils.DataSourceRequestUtil; +import com.yfd.platform.utils.SecurityUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + *

+ * 账号密码策略 前端控制器 + *

+ */ +@Slf4j +@RestController +@RequestMapping("/system/acctPasswordPolicy") +@Tag(name = "账号密码策略管理") +public class SysAcctPasswordPolicyController { + + @Resource + private ISysAcctPasswordPolicyService policyService; + + @Resource + private IUserService userService; + + @GetMapping("/current") + @Operation(summary = "获取当前生效的密码策略") + public ResponseResult current() { + SysAcctPasswordPolicy policy = policyService.getCurrentPolicy(); + return policy == null + ? ResponseResult.error("未配置密码策略") + : ResponseResult.successData(policy); + } + + @GetMapping("/getById") + @Operation(summary = "根据ID查询密码策略") + public ResponseResult getById(@RequestParam String id) { + SysAcctPasswordPolicy policy = policyService.getById(id); + return policy == null + ? ResponseResult.error("策略不存在") + : ResponseResult.successData(policy); + } + + @PostMapping("/queryPageList") + @Operation(summary = "分页查询密码策略列表") + public ResponseResult queryPageList(@RequestBody DataSourceRequest request) { + Page page = DataSourceRequestUtil.executeQuery(request, SysAcctPasswordPolicy.class, policyService); + return ResponseResult.successData(page); + } + + @PostMapping("/list") + @Operation(summary = "查询密码策略列表(不分页)") + public ResponseResult list(@RequestBody DataSourceRequest request) { + List list = DataSourceRequestUtil.executeList(request, SysAcctPasswordPolicy.class, policyService); + return ResponseResult.successData(list); + } + + @PostMapping("/add") + @Operation(summary = "新增密码策略") + public ResponseResult add(@RequestBody SysAcctPasswordPolicy policy) { + if (StrUtil.isBlank(policy.getName())) { + return ResponseResult.error("策略名称不能为空"); + } + policy.setId(IdUtil.fastSimpleUUID()); +// policy.setRecordUser(SecurityUtils.getUserId()); + policy.setRecordUser("admin"); + policy.setRecordTime(new Date()); + policy.setIsDeleted(0); + + boolean ok = policyService.save(policy); + return ok ? ResponseResult.success("新增成功") : ResponseResult.error("新增失败"); + } + + @PostMapping("/update") + @Operation(summary = "修改密码策略") + public ResponseResult update(@RequestBody SysAcctPasswordPolicy policy) { + if (StrUtil.isBlank(policy.getId())) { + return ResponseResult.error("ID不能为空"); + } + SysAcctPasswordPolicy exist = policyService.getById(policy.getId()); + if (exist == null) { + return ResponseResult.error("策略不存在"); + } + policy.setModifyUser(userService.getUsername()); + policy.setModifyTime(new Date()); + + boolean ok = policyService.updateById(policy); + return ok ? ResponseResult.success("修改成功") : ResponseResult.error("修改失败"); + } + + @PostMapping("/delete") + @Operation(summary = "删除密码策略(软删除)") + public ResponseResult delete(@RequestBody List ids) { + if (ids == null || ids.isEmpty()) { + return ResponseResult.error("ID不能为空"); + } + Date now = new Date(); + String username = userService.getUsername(); + for (String id : ids) { + SysAcctPasswordPolicy policy = policyService.getById(id); + if (policy != null) { + policy.setIsDeleted(1); + policy.setDeleteUser(username); + policy.setDeleteTime(now); + policyService.updateById(policy); + } + } + return ResponseResult.success("删除成功"); + } + + @PostMapping("/validatePassword") + @Operation(summary = "根据当前策略校验密码复杂度") + public ResponseResult validatePassword(@RequestParam String password, + @RequestParam(required = false) String username) { + SysAcctPasswordPolicy policy = policyService.getCurrentPolicy(); + if (policy == null) { + return ResponseResult.error("未配置密码策略"); + } + String errMsg = policyService.validatePassword(policy, password, username); + if (errMsg != null) { + return ResponseResult.error(errMsg); + } + return ResponseResult.success("密码校验通过"); + } +} diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysAcctPasswordPolicy.java b/backend/src/main/java/com/yfd/platform/system/domain/SysAcctPasswordPolicy.java new file mode 100644 index 00000000..86c838e0 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/domain/SysAcctPasswordPolicy.java @@ -0,0 +1,189 @@ +package com.yfd.platform.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 账号密码策略表 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("SYS_ACCT_PASSWORD_POLICY") +public class SysAcctPasswordPolicy implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** 密码策略标识 */ + private String code; + + /** 策略名称 */ + private String name; + + /** 用户名敏感性校验 */ + private Integer usernameSensitiveValid; + + /** 密码敏感性校验 */ + private Integer passwordSensitiveValid; + + /** 启用短信重置密码 */ + private Integer enableSmsResetPwd; + + /** 启用邮箱重置密码 */ + private Integer enableEmailResetPwd; + + /** 启用输入密码 */ + private Integer enableInputPassword; + + /** 启用默认密码 */ + private Integer enableDefaultPassword; + + /** 默认密码 */ + private String defaultPassword; + + /** 密码最小长度 */ + private Integer minLength; + + /** 密码最大长度 */ + private Integer maxLength; + + /** 最大密码错误次数 */ + private Integer maxErrorNumber; + + /** 密码必须包含字符类型 */ + private String passwordCharsRequire; + + /** 密码数字数量要求 */ + private Integer digitsCount; + + /** 密码小写字母数量要求 */ + private Integer lowercaseCount; + + /** 密码大写字母数量要求 */ + private Integer uppercaseCount; + + /** 特殊字符数量要求 */ + private Integer specialCharCount; + + /** 密码不可与用户名相同 */ + private Integer notUsername; + + /** 密码正则表达式 */ + private String regularExpression; + + /** 不可使用最近N次密码 */ + private Integer notRecentCount; + + /** 启用密码策略 */ + private Integer enablePasswordPolicy; + + /** 启用安全策略 */ + private Integer enableSecurityPolicy; + + /** 启用账户锁定 */ + private Integer enableLock; + + /** 锁定时长(秒) */ + private Integer lockedExpireTime; + + /** 启用验证码 */ + private Integer enableCaptcha; + + /** 验证码类型 */ + private String captureType; + + /** 触发验证码的错误次数 */ + private Integer maxCheckCaptcha; + + /** Web端多端登录 */ + private Integer enableWebMultipleLogin; + + /** 移动端多端登录 */ + private Integer enableAppMultipleLogin; + + /** 密码更新频率(天) */ + private Integer passwordUpdateRate; + + /** 规则变更时强制修改密码 */ + private Integer forceModifyPwdRuleModify; + + /** 密码修改提醒周期(天) */ + private Integer passwordReminderPeriod; + + /** 首次登录强制修改密码 */ + private Integer forceModifyPassword; + + /** 允许账号登录 */ + private Integer enableAccountLogin; + + /** 修改密码后需重新登录 */ + private Integer loginAgain; + + /** 允许短信登录 */ + private Integer enableSmsLogin; + + /** 启用随机密码 */ + private Integer enableRandomPassword; + + /** 启用数据加密 */ + private Integer enableDataSecurity; + + /** 是否全局策略 */ + private Integer isBelongGlobal; + + /** 是否双重验证 */ + private Integer isTwoFactorAuth; + + /** 创建人 */ + @TableField(fill = FieldFill.INSERT) + private String recordUser; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private Date recordTime; + + /** 修改人 */ + @TableField(fill = FieldFill.UPDATE) + private String modifyUser; + + /** 修改时间 */ + @TableField(fill = FieldFill.UPDATE) + private Date modifyTime; + + /** 是否已删除 0=未删除 1=已删除 */ + @TableLogic + private Integer isDeleted; + + /** 删除人 */ + private String deleteUser; + + /** 删除时间 */ + private Date deleteTime; + + /** 过滤内容 */ + private String filterContent; + + /** 排序 */ + private Integer orderIndex; + + /** 启用验证码类型 */ + private String enableCaptchaType; + + /** 启用企业微信登录 */ + private Integer enableEnterpriseWechatLogin; + + /** 启用钉钉登录 */ + private Integer enableDingdingLogin; + + /** 启用微信登录 */ + private Integer enableWechatLogin; +} diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysAcctPasswordPolicyMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysAcctPasswordPolicyMapper.java new file mode 100644 index 00000000..b89fb6e7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysAcctPasswordPolicyMapper.java @@ -0,0 +1,14 @@ +package com.yfd.platform.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yfd.platform.system.domain.SysAcctPasswordPolicy; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 账号密码策略表 Mapper 接口 + *

+ */ +@Mapper +public interface SysAcctPasswordPolicyMapper extends BaseMapper { +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISysAcctPasswordPolicyService.java b/backend/src/main/java/com/yfd/platform/system/service/ISysAcctPasswordPolicyService.java new file mode 100644 index 00000000..eaa81b98 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/ISysAcctPasswordPolicyService.java @@ -0,0 +1,26 @@ +package com.yfd.platform.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yfd.platform.system.domain.SysAcctPasswordPolicy; + +/** + *

+ * 账号密码策略表 服务类 + *

+ */ +public interface ISysAcctPasswordPolicyService extends IService { + + /** + * 获取当前生效的策略(取排序最小且未删除的那条) + */ + SysAcctPasswordPolicy getCurrentPolicy(); + + /** + * 根据策略校验密码复杂度 + * + * @param password 待校验的密码 + * @param username 关联的用户名(用于NOT_USERNAME校验) + * @return null=校验通过,否则返回错误提示 + */ + String validatePassword(SysAcctPasswordPolicy policy, String password, String username); +} diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SysAcctPasswordPolicyServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SysAcctPasswordPolicyServiceImpl.java new file mode 100644 index 00000000..2c4f569b --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SysAcctPasswordPolicyServiceImpl.java @@ -0,0 +1,95 @@ +package com.yfd.platform.system.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.system.domain.SysAcctPasswordPolicy; +import com.yfd.platform.system.mapper.SysAcctPasswordPolicyMapper; +import com.yfd.platform.system.service.ISysAcctPasswordPolicyService; +import org.springframework.stereotype.Service; + +import java.util.regex.Pattern; + +/** + *

+ * 账号密码策略表 服务实现类 + *

+ */ +@Service +public class SysAcctPasswordPolicyServiceImpl extends ServiceImpl + implements ISysAcctPasswordPolicyService { + + @Override + public SysAcctPasswordPolicy getCurrentPolicy() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysAcctPasswordPolicy::getIsDeleted, 0) + .eq(SysAcctPasswordPolicy::getEnablePasswordPolicy, 1) + .orderByAsc(SysAcctPasswordPolicy::getOrderIndex) + .last("LIMIT 1"); + return getOne(wrapper); + } + + @Override + public String validatePassword(SysAcctPasswordPolicy policy, String password, String username) { + if (policy == null || StrUtil.isBlank(password)) { + return "密码不能为空"; + } + + // 1. 密码长度校验 + if (policy.getMinLength() != null && password.length() < policy.getMinLength()) { + return "密码长度不能少于" + policy.getMinLength() + "位"; + } + if (policy.getMaxLength() != null && password.length() > policy.getMaxLength()) { + return "密码长度不能超过" + policy.getMaxLength() + "位"; + } + + // 2. 密码与用户名不可相同 + if (isEnabled(policy.getNotUsername()) && StrUtil.isNotBlank(username) && username.equals(password)) { + return "密码不能与用户名相同"; + } + + // 3. 字符类型数量校验 + int digits = 0, lowercase = 0, uppercase = 0, special = 0; + for (char c : password.toCharArray()) { + if (Character.isDigit(c)) { + digits++; + } else if (Character.isLowerCase(c)) { + lowercase++; + } else if (Character.isUpperCase(c)) { + uppercase++; + } else { + special++; + } + } + + if (policy.getDigitsCount() != null && digits < policy.getDigitsCount()) { + return "密码至少包含" + policy.getDigitsCount() + "个数字"; + } + if (policy.getLowercaseCount() != null && lowercase < policy.getLowercaseCount()) { + return "密码至少包含" + policy.getLowercaseCount() + "个小写字母"; + } + if (policy.getUppercaseCount() != null && uppercase < policy.getUppercaseCount()) { + return "密码至少包含" + policy.getUppercaseCount() + "个大写字母"; + } + if (policy.getSpecialCharCount() != null && special < policy.getSpecialCharCount()) { + return "密码至少包含" + policy.getSpecialCharCount() + "个特殊字符"; + } + + // 4. 正则表达式校验 + if (StrUtil.isNotBlank(policy.getRegularExpression())) { + try { + if (!Pattern.matches(policy.getRegularExpression(), password)) { + return "密码格式不符合要求"; + } + } catch (Exception e) { + // 正则不合法则跳过 + } + } + + return null; + } + + private boolean isEnabled(Integer value) { + return value != null && value == 1; + } +}