RolePasswords.java 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package com.dms.service;
  2. import com.dms.common.DmsException;
  3. import com.dms.common.Errors;
  4. import com.dms.security.Passwords;
  5. import java.util.ArrayList;
  6. import java.util.LinkedHashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Objects;
  10. import java.util.Set;
  11. /**
  12. * 角色密码分配解析与校验(契约1.53 密码即角色):创建/编辑用户与门户
  13. * 设密三入口共用。请求形态 [{roleId, password}]——password 为 null/缺省
  14. * 表示该角色使用用户级密码。硬约束(消除登录匹配歧义):
  15. * ①必须为一个角色选择使用用户密码(恰一个 null);
  16. * ②其余独立密码两两互异;
  17. * ③独立密码不等于用户级密码(创建/设密比对明文,编辑用旧哈希 verify)。
  18. */
  19. public final class RolePasswords {
  20. private RolePasswords() {
  21. }
  22. /**
  23. * 解析并校验角色密码分配。
  24. *
  25. * @param raw 请求体 rolePasswords 数组
  26. * @param targetRoleIds 本次挂载的角色ID全集(调用方保证 ≥2)
  27. * @param userPasswordPlaintext 用户级密码明文(创建/设密场景提供)
  28. * @param userPasswordHash 用户级密码哈希(编辑未改用户密码场景提供)
  29. * @return roleId → 独立密码明文;使用用户密码的角色映射值为 null
  30. */
  31. public static Map<Long, String> parse(Object raw, Set<Long> targetRoleIds,
  32. String userPasswordPlaintext,
  33. String userPasswordHash) {
  34. if (!(raw instanceof List<?> list)) {
  35. throw Errors.invalidArgument("rolePasswords必须是数组");
  36. }
  37. Map<Long, String> result = new LinkedHashMap<>();
  38. for (Object item : list) {
  39. if (!(item instanceof Map<?, ?> entry)
  40. || !(entry.get("roleId") instanceof String roleIdText)) {
  41. throw Errors.invalidArgument("rolePasswords每项须为含roleId的对象");
  42. }
  43. long roleId;
  44. try {
  45. roleId = Long.parseLong(roleIdText);
  46. } catch (NumberFormatException ex) {
  47. throw Errors.invalidArgument("roleId必须是数字字符串");
  48. }
  49. if (!targetRoleIds.contains(roleId)) {
  50. throw Errors.invalidArgument("roleId必须指向本次挂载的角色");
  51. }
  52. if (result.containsKey(roleId)) {
  53. throw Errors.invalidArgument("rolePasswords存在重复角色");
  54. }
  55. Object password = entry.get("password");
  56. if (password instanceof String text && !text.isEmpty()) {
  57. try {
  58. Passwords.validatePolicy(text);
  59. } catch (IllegalArgumentException ex) {
  60. throw Errors.invalidArgument(ex.getMessage());
  61. }
  62. result.put(roleId, text);
  63. } else {
  64. result.put(roleId, null);
  65. }
  66. }
  67. if (!result.keySet().equals(targetRoleIds)) {
  68. throw Errors.invalidArgument("rolePasswords必须覆盖全部挂载角色");
  69. }
  70. long nullCount = result.values().stream().filter(Objects::isNull).count();
  71. if (nullCount != 1) {
  72. throw Errors.invalidArgument("必须为一个角色选择使用用户密码");
  73. }
  74. List<String> independents = new ArrayList<>();
  75. for (String password : result.values()) {
  76. if (password == null) {
  77. continue;
  78. }
  79. if ((userPasswordPlaintext != null && password.equals(userPasswordPlaintext))
  80. || (userPasswordHash != null
  81. && Passwords.verify(userPasswordHash, password))) {
  82. throw conflict();
  83. }
  84. for (String prev : independents) {
  85. if (prev.equals(password)) {
  86. throw conflict();
  87. }
  88. }
  89. independents.add(password);
  90. }
  91. return result;
  92. }
  93. /** 互异约束冲突:登录按密码匹配角色,同密码会产生进入歧义。 */
  94. public static DmsException conflict() {
  95. return new DmsException("PASSWORD_CONFLICT", 400,
  96. "各角色密码不能相同,且不能与用户密码相同", null);
  97. }
  98. }