SecurityAdminController.java 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package com.example.hrlab.security;
  2. import com.example.hrlab.audit.AuditService;
  3. import com.example.hrlab.common.ApiResponse;
  4. import com.example.hrlab.common.BusinessException;
  5. import com.example.hrlab.common.ErrorCodes;
  6. import com.example.hrlab.common.Rows;
  7. import jakarta.validation.Valid;
  8. import jakarta.validation.constraints.NotBlank;
  9. import java.util.List;
  10. import java.util.Map;
  11. import org.springframework.http.HttpStatus;
  12. import org.springframework.jdbc.core.JdbcTemplate;
  13. import org.springframework.security.access.prepost.PreAuthorize;
  14. import org.springframework.transaction.annotation.Transactional;
  15. import org.springframework.web.bind.annotation.DeleteMapping;
  16. import org.springframework.web.bind.annotation.GetMapping;
  17. import org.springframework.web.bind.annotation.PathVariable;
  18. import org.springframework.web.bind.annotation.PostMapping;
  19. import org.springframework.web.bind.annotation.PutMapping;
  20. import org.springframework.web.bind.annotation.RequestBody;
  21. import org.springframework.web.bind.annotation.RequestMapping;
  22. import org.springframework.web.bind.annotation.RestController;
  23. @RestController
  24. @RequestMapping("/api/v1/security")
  25. public class SecurityAdminController {
  26. private final JdbcTemplate jdbcTemplate;
  27. private final AuditService auditService;
  28. public SecurityAdminController(JdbcTemplate jdbcTemplate, AuditService auditService) {
  29. this.jdbcTemplate = jdbcTemplate;
  30. this.auditService = auditService;
  31. }
  32. @GetMapping("/roles")
  33. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  34. public ApiResponse<Object> roles() {
  35. return ApiResponse.ok(jdbcTemplate.queryForList("""
  36. SELECT *, role_code IN ('SYSTEM_ADMIN','HR_ADMIN','RECRUITER','DEPT_MANAGER','PAYROLL_ADMIN',
  37. 'EMPLOYEE_SELF','AUDITOR','INTEGRATION_CLIENT') AS builtin
  38. FROM roles ORDER BY role_code
  39. """));
  40. }
  41. @GetMapping("/permissions")
  42. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  43. public ApiResponse<Object> permissions() {
  44. return ApiResponse.ok(jdbcTemplate.queryForList("""
  45. SELECT *, permission_code IN ('system:admin','employee:write','employee:read','recruitment:write',
  46. 'attendance:write','payroll:write','performance:write','report:export','audit:read','integration:access') AS builtin
  47. FROM permissions ORDER BY permission_code
  48. """));
  49. }
  50. @PostMapping("/roles")
  51. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  52. public ApiResponse<Map<String, Object>> createRole(@Valid @RequestBody RoleRequest request) {
  53. Long id = jdbcTemplate.queryForObject("""
  54. INSERT INTO roles(role_code, role_name, data_scope) VALUES (?, ?, ?) RETURNING id
  55. """, Long.class, request.roleCode(), request.roleName(), request.dataScope());
  56. auditService.record("ROLE_CREATE", "roles", id, null, request);
  57. return ApiResponse.ok(findRole(id));
  58. }
  59. @PutMapping("/roles/{id}")
  60. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  61. public ApiResponse<Map<String, Object>> updateRole(@PathVariable Long id, @Valid @RequestBody RoleRequest request) {
  62. Map<String, Object> before = findRole(id);
  63. SecurityCatalogPolicy.requireRoleCodeMutable(String.valueOf(before.get("role_code")), request.roleCode());
  64. jdbcTemplate.update("UPDATE roles SET role_code = ?, role_name = ?, data_scope = ?, updated_at = now() WHERE id = ?",
  65. request.roleCode(), request.roleName(), request.dataScope(), id);
  66. auditService.record("ROLE_UPDATE", "roles", id, before, request);
  67. return ApiResponse.ok(findRole(id));
  68. }
  69. @DeleteMapping("/roles/{id}")
  70. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  71. public ApiResponse<Map<String, Object>> deleteRole(@PathVariable Long id) {
  72. Map<String, Object> before = findRole(id);
  73. String roleCode = String.valueOf(before.get("role_code"));
  74. SecurityCatalogPolicy.requireRoleDeletable(roleCode);
  75. Long boundUsers = jdbcTemplate.queryForObject("SELECT count(*) FROM user_roles WHERE role_id = ?", Long.class, id);
  76. if (boundUsers != null && boundUsers > 0) {
  77. throw new BusinessException(ErrorCodes.BUSINESS_RULE_VIOLATION, "角色已分配给用户,不能删除");
  78. }
  79. jdbcTemplate.update("DELETE FROM roles WHERE id = ?", id);
  80. auditService.record("ROLE_DELETE", "roles", id, before, Map.of("deleted", true));
  81. return ApiResponse.ok(Map.of("deleted", true));
  82. }
  83. @GetMapping("/roles/{id}/permissions")
  84. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  85. public ApiResponse<Object> rolePermissions(@PathVariable Long id) {
  86. findRole(id);
  87. return ApiResponse.ok(permissionsForRole(id));
  88. }
  89. @PutMapping("/roles/{id}/permissions")
  90. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  91. @Transactional
  92. public ApiResponse<Map<String, Object>> replaceRolePermissions(@PathVariable Long id,
  93. @RequestBody PermissionCodesRequest request) {
  94. findRole(id);
  95. Map<String, Object> before = Map.of("permissions", permissionsForRole(id));
  96. jdbcTemplate.update("DELETE FROM role_permissions WHERE role_id = ?", id);
  97. for (String code : request.permissionCodes()) {
  98. jdbcTemplate.update("""
  99. INSERT INTO role_permissions(role_id, permission_id)
  100. SELECT ?, id FROM permissions WHERE permission_code = ?
  101. """, id, code);
  102. }
  103. Map<String, Object> after = Map.of("permissions", permissionsForRole(id));
  104. auditService.record("ROLE_PERMISSION_REPLACE", "roles", id, before, after);
  105. return ApiResponse.ok(after);
  106. }
  107. @PostMapping("/permissions")
  108. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  109. public ApiResponse<Map<String, Object>> createPermission(@Valid @RequestBody PermissionRequest request) {
  110. Long id = jdbcTemplate.queryForObject("""
  111. INSERT INTO permissions(permission_code, permission_name) VALUES (?, ?) RETURNING id
  112. """, Long.class, request.permissionCode(), request.permissionName());
  113. auditService.record("PERMISSION_CREATE", "permissions", id, null, request);
  114. return ApiResponse.ok(findPermission(id));
  115. }
  116. @PutMapping("/permissions/{id}")
  117. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  118. public ApiResponse<Map<String, Object>> updatePermission(@PathVariable Long id, @Valid @RequestBody PermissionRequest request) {
  119. Map<String, Object> before = findPermission(id);
  120. SecurityCatalogPolicy.requirePermissionCodeMutable(String.valueOf(before.get("permission_code")), request.permissionCode());
  121. jdbcTemplate.update("UPDATE permissions SET permission_code = ?, permission_name = ?, updated_at = now() WHERE id = ?",
  122. request.permissionCode(), request.permissionName(), id);
  123. auditService.record("PERMISSION_UPDATE", "permissions", id, before, request);
  124. return ApiResponse.ok(findPermission(id));
  125. }
  126. @DeleteMapping("/permissions/{id}")
  127. @PreAuthorize("hasRole('SYSTEM_ADMIN')")
  128. @Transactional
  129. public ApiResponse<Map<String, Object>> deletePermission(@PathVariable Long id) {
  130. Map<String, Object> before = findPermission(id);
  131. SecurityCatalogPolicy.requirePermissionDeletable(String.valueOf(before.get("permission_code")));
  132. jdbcTemplate.update("DELETE FROM role_permissions WHERE permission_id = ?", id);
  133. jdbcTemplate.update("DELETE FROM permissions WHERE id = ?", id);
  134. auditService.record("PERMISSION_DELETE", "permissions", id, before, Map.of("deleted", true));
  135. return ApiResponse.ok(Map.of("deleted", true));
  136. }
  137. private List<String> permissionsForRole(Long roleId) {
  138. return jdbcTemplate.queryForList("""
  139. SELECT p.permission_code FROM permissions p
  140. JOIN role_permissions rp ON rp.permission_id = p.id
  141. WHERE rp.role_id = ?
  142. ORDER BY p.permission_code
  143. """, String.class, roleId);
  144. }
  145. private Map<String, Object> findRole(Long id) {
  146. return jdbcTemplate.query("SELECT * FROM roles WHERE id = ?", rs -> {
  147. if (!rs.next()) {
  148. throw new BusinessException(ErrorCodes.NOT_FOUND, "role not found", HttpStatus.NOT_FOUND);
  149. }
  150. return Rows.map(rs);
  151. }, id);
  152. }
  153. private Map<String, Object> findPermission(Long id) {
  154. return jdbcTemplate.query("SELECT * FROM permissions WHERE id = ?", rs -> {
  155. if (!rs.next()) {
  156. throw new BusinessException(ErrorCodes.NOT_FOUND, "权限不存在", HttpStatus.NOT_FOUND);
  157. }
  158. return Rows.map(rs);
  159. }, id);
  160. }
  161. public record RoleRequest(@NotBlank String roleCode, @NotBlank String roleName, @NotBlank String dataScope) {
  162. }
  163. public record PermissionRequest(@NotBlank String permissionCode, @NotBlank String permissionName) {
  164. }
  165. public record PermissionCodesRequest(List<String> permissionCodes) {
  166. }
  167. }