{"record":{"id":"92832c3e5677668e","repo":"lenve/vhr","slug":"error-92832c","errorCode":null,"errorMessage":"权限不足，请联系管理员!","messagePattern":"权限不足，请联系管理员!","errorType":"exception","errorClass":"AccessDeniedException","httpStatus":403,"severity":"error","filePath":"vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/CustomUrlDecisionManager.java","lineNumber":43,"sourceCode":"    @Override\n    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {\n        for (ConfigAttribute configAttribute : configAttributes) {\n            String needRole = configAttribute.getAttribute();\n            if (\"ROLE_LOGIN\".equals(needRole)) {\n                if (authentication instanceof AnonymousAuthenticationToken) {\n                    throw new AccessDeniedException(\"尚未登录，请登录!\");\n                }else {\n                    return;\n                }\n            }\n            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();\n            for (GrantedAuthority authority : authorities) {\n                if (authority.getAuthority().equals(needRole)) {\n                    return;\n                }\n            }\n        }\n        throw new AccessDeniedException(\"权限不足，请联系管理员!\");\n    }\n\n    @Override\n    public boolean supports(ConfigAttribute attribute) {\n        return true;\n    }\n\n    @Override\n    public boolean supports(Class<?> clazz) {\n        return true;\n    }\n}\n","sourceCodeStart":25,"sourceCodeEnd":56,"githubUrl":"https://github.com/lenve/vhr/blob/03abbd35af24e55368ce4e09f4038dc2aba3ff5f/vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/CustomUrlDecisionManager.java#L25-L56","documentation":"Thrown at the end of CustomUrlDecisionManager.decide when the authenticated user holds none of the ConfigAttributes required by the requested resource. The method iterates every required role and the user's GrantedAuthority list; if no authority string matches any required role, control falls through the loop and the final throw executes — a hard authorization denial for an otherwise authenticated principal.","triggerScenarios":"A logged-in Hr user requests a URL whose FilterInvocationSecurityMetadataSource returned one or more role names (e.g. 'ROLE_admin', 'system:basic') that are absent from that user's authorities. This happens because the hr_role / menu-role mapping for that user does not include the role guarding the endpoint, so the authority loop never returns.","commonSituations":"A new endpoint was added to a menu/role but the current user's role set wasn't updated; roles were re-cached (e.g., in SecurityConfig or a Redis role cache) and are stale; the user logged in before their role was granted; the menu→role seed data drifted from the code; an admin revoked the role but the user's session still carries old authorities that still don't match.","solutions":["In the vhr admin UI, assign the missing role to the user (HrService.updateHrRole / hr_role table) so the required ConfigAttribute appears in their authorities, then have them re-login to refresh authorities.","Inspect MenuService.getMenusByHrId / the role metadata source to confirm the endpoint's required role is correctly derived from the menu table and not returning a stale/wrong attribute.","Clear any cached role/authority data (Redis, in-memory) and force a fresh login so authorities are recomputed from current hr_role rows.","Verify the URL pattern in FilterInvocationSecurityMetadataSource matches the endpoint you intend to protect with that role (a too-broad pattern can demand a role the legitimate user lacks).","If the denial is expected, ensure the front-end shows a permission-denied state instead of retrying, and confirm the AccessDeniedHandler returns a clean 403 JSON."],"exampleFix":"// Backend: ensure role assignment reflects intent\n// before — user lacks role after endpoint added\nINSERT INTO hr_role (hrid, rid) VALUES (:hrid, :requiredRoleId);\n// after — also clear stale authorities by forcing re-authentication\n// (have the affected user log out and back in, or invalidate their session)\nsessionRegistry.getAllSessions(principal, false).forEach(SessionInformation::expireNow);","handlingStrategy":"validation","validationCode":"// Front-end guard: hide controls/routes the current user lacks the role for.\nconst hasRole = (need) => (currentUser.roles || []).includes(need);\nif (!hasRole('admin')) router.push('/403');\n// Backend: the AccessDecisionManager already enforces it; this just improves UX so the\n// user never triggers the denial in the first place.","typeGuard":"// Server-side: check authorities before doing privileged work outside the filter chain.\nboolean allowed = authentication.getAuthorities().stream()\n    .map(GrantedAuthority::getAuthority)\n    .anyMatch(needRole::equals);\nif (!allowed) throw new AccessDeniedException(\"权限不足，请联系管理员!\");","tryCatchPattern":"// Centralized handler translating the denial to a clean 403:\n@Component\npublic class CustomAccessDeniedHandler implements AccessDeniedHandler {\n    @Override\n    public void handle(HttpServletRequest req, HttpServletResponse resp,\n                       AccessDeniedException ex) throws IOException {\n        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);\n        resp.setContentType(\"application/json;charset=UTF-8\");\n        resp.getWriter().write(new ObjectMapper().writeValueAsString(\n            Map.of(\"status\", 403, \"msg\", \"权限不足，请联系管理员!\")));\n    }\n}","preventionTips":["Derive front-end menu/buttons from the same role source the security metadata uses, so users never see actions they can't perform.","After granting/revoking a role, invalidate the affected session so authorities refresh on next request.","Keep menu→role seed data and code-configured URL patterns in sync; add a test that asserts each protected URL has exactly the intended attributes.","Cache roles with a TTL and bust the cache on role changes to avoid stale-authority denials."],"tags":["spring-security","access-control","authorization","roles","rbac"],"backgroundTag":null,"analyzedSha":"03abbd35af24e55368ce4e09f4038dc2aba3ff5f","analyzedAt":"2026-08-14T04:43:34.269Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}