| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- package com.dms.service;
- import com.dms.common.DmsException;
- import com.dms.common.Errors;
- import com.dms.security.Passwords;
- import java.util.ArrayList;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- import java.util.Set;
- /**
- * 角色密码分配解析与校验(契约1.53 密码即角色):创建/编辑用户与门户
- * 设密三入口共用。请求形态 [{roleId, password}]——password 为 null/缺省
- * 表示该角色使用用户级密码。硬约束(消除登录匹配歧义):
- * ①必须为一个角色选择使用用户密码(恰一个 null);
- * ②其余独立密码两两互异;
- * ③独立密码不等于用户级密码(创建/设密比对明文,编辑用旧哈希 verify)。
- */
- public final class RolePasswords {
- private RolePasswords() {
- }
- /**
- * 解析并校验角色密码分配。
- *
- * @param raw 请求体 rolePasswords 数组
- * @param targetRoleIds 本次挂载的角色ID全集(调用方保证 ≥2)
- * @param userPasswordPlaintext 用户级密码明文(创建/设密场景提供)
- * @param userPasswordHash 用户级密码哈希(编辑未改用户密码场景提供)
- * @return roleId → 独立密码明文;使用用户密码的角色映射值为 null
- */
- public static Map<Long, String> parse(Object raw, Set<Long> targetRoleIds,
- String userPasswordPlaintext,
- String userPasswordHash) {
- if (!(raw instanceof List<?> list)) {
- throw Errors.invalidArgument("rolePasswords必须是数组");
- }
- Map<Long, String> result = new LinkedHashMap<>();
- for (Object item : list) {
- if (!(item instanceof Map<?, ?> entry)
- || !(entry.get("roleId") instanceof String roleIdText)) {
- throw Errors.invalidArgument("rolePasswords每项须为含roleId的对象");
- }
- long roleId;
- try {
- roleId = Long.parseLong(roleIdText);
- } catch (NumberFormatException ex) {
- throw Errors.invalidArgument("roleId必须是数字字符串");
- }
- if (!targetRoleIds.contains(roleId)) {
- throw Errors.invalidArgument("roleId必须指向本次挂载的角色");
- }
- if (result.containsKey(roleId)) {
- throw Errors.invalidArgument("rolePasswords存在重复角色");
- }
- Object password = entry.get("password");
- if (password instanceof String text && !text.isEmpty()) {
- try {
- Passwords.validatePolicy(text);
- } catch (IllegalArgumentException ex) {
- throw Errors.invalidArgument(ex.getMessage());
- }
- result.put(roleId, text);
- } else {
- result.put(roleId, null);
- }
- }
- if (!result.keySet().equals(targetRoleIds)) {
- throw Errors.invalidArgument("rolePasswords必须覆盖全部挂载角色");
- }
- long nullCount = result.values().stream().filter(Objects::isNull).count();
- if (nullCount != 1) {
- throw Errors.invalidArgument("必须为一个角色选择使用用户密码");
- }
- List<String> independents = new ArrayList<>();
- for (String password : result.values()) {
- if (password == null) {
- continue;
- }
- if ((userPasswordPlaintext != null && password.equals(userPasswordPlaintext))
- || (userPasswordHash != null
- && Passwords.verify(userPasswordHash, password))) {
- throw conflict();
- }
- for (String prev : independents) {
- if (prev.equals(password)) {
- throw conflict();
- }
- }
- independents.add(password);
- }
- return result;
- }
- /** 互异约束冲突:登录按密码匹配角色,同密码会产生进入歧义。 */
- public static DmsException conflict() {
- return new DmsException("PASSWORD_CONFLICT", 400,
- "各角色密码不能相同,且不能与用户密码相同", null);
- }
- }
|