spring-projects/spring-security · error · AccessDeniedException
Access is denied
Error message
Access is denied
What it means
AffirmativeBased's decide() grants access as soon as any AccessDecisionVoter votes ACCESS_GRANTED; it only throws this AccessDeniedException when no voter granted access AND at least one voted ACCESS_DENIED (deny > 0). It fires when the authenticated user's authorities satisfy none of the voters (e.g. RoleVoter/PreInvocationAuthorizationAdviceVoter) for the secured invocation, so the deny votes carry the decision. If all voters abstained, decide() returns without exception instead.
Source
Thrown at access/src/main/java/org/springframework/security/access/vote/AffirmativeBased.java:76
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(
this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
View on GitHub (pinned to 96852e8860)
Solutions
- Grant the user the missing role/authority the denying voter requires
- Remove or adjust the voter/attribute that inappropriately denies
- Switch strategy to ConsensusBased or UnanimousBased if a single deny should not block
- Log each voter's decision to identify which voter denied
Example fix
// before <intercept-url pattern="/admin/**" access="ROLE_ADMIN,ROLE_USER"/> // user has ROLE_USER only; RoleVoter denies ROLE_ADMIN // after <intercept-url pattern="/admin/**" access="ROLE_ADMIN"/> // or grant the user ROLE_ADMIN
Defensive patterns
Strategy: try-catch
Validate before calling
boolean hasAllRoles(Authentication a, List<String> roles) {
var granted = a.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
return granted.containsAll(roles);
} Type guard
null
Try / catch
try {
affirmativeBased.decide(auth, object, attrs);
} catch (AccessDeniedException e) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "A voter denied access");
} Prevention
- Grant users the full role sets referenced by security attributes
- Audit for voters that deny attributes they should abstain on
- Log voter decisions in staging to catch unexpected denies
When it happens
Trigger: During method/web security checks, one AccessDecisionVoter returns ACCESS_DENIED for the invocation even though others may have granted.
Common situations: A user lacks one of several required roles while another role matched (e.g. @PreAuthorize replaced by role voters with overlapping attributes); misconfigured voter denies attributes it should abstain on.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/b48110e6dc208daf.
Report an issue: GitHub.