lenve/vhr · error · AccessDeniedException

权限不足,请联系管理员!

Error message

权限不足,请联系管理员!

What it means

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.

Source

Thrown at vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/CustomUrlDecisionManager.java:43

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            String needRole = configAttribute.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException("尚未登录,请登录!");
                }else {
                    return;
                }
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员!");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

View on GitHub (pinned to 03abbd35af)

Solutions

  1. 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.
  2. 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.
  3. Clear any cached role/authority data (Redis, in-memory) and force a fresh login so authorities are recomputed from current hr_role rows.
  4. 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).
  5. 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.

Example fix

// Backend: ensure role assignment reflects intent
// before — user lacks role after endpoint added
INSERT INTO hr_role (hrid, rid) VALUES (:hrid, :requiredRoleId);
// after — also clear stale authorities by forcing re-authentication
// (have the affected user log out and back in, or invalidate their session)
sessionRegistry.getAllSessions(principal, false).forEach(SessionInformation::expireNow);
Defensive patterns

Strategy: validation

Validate before calling

// Front-end guard: hide controls/routes the current user lacks the role for.
const hasRole = (need) => (currentUser.roles || []).includes(need);
if (!hasRole('admin')) router.push('/403');
// Backend: the AccessDecisionManager already enforces it; this just improves UX so the
// user never triggers the denial in the first place.

Type guard

// Server-side: check authorities before doing privileged work outside the filter chain.
boolean allowed = authentication.getAuthorities().stream()
    .map(GrantedAuthority::getAuthority)
    .anyMatch(needRole::equals);
if (!allowed) throw new AccessDeniedException("权限不足,请联系管理员!");

Try / catch

// Centralized handler translating the denial to a clean 403:
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest req, HttpServletResponse resp,
                       AccessDeniedException ex) throws IOException {
        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        resp.setContentType("application/json;charset=UTF-8");
        resp.getWriter().write(new ObjectMapper().writeValueAsString(
            Map.of("status", 403, "msg", "权限不足,请联系管理员!")));
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of lenve/vhr@03abbd35af (2026-08-14). Data as JSON: /api/errors/92832c3e5677668e. Report an issue: GitHub.