spring-projects/spring-security · error · AccessDeniedException
Access is denied
Error message
Access is denied
What it means
ConsensusBased.decide() throws AccessDeniedException when deny votes outnumber grant votes (deny > grant). The consensus strategy totals ACCESS_GRANTED vs ACCESS_DENIED votes and denies when the majority denies.
Source
Thrown at access/src/main/java/org/springframework/security/access/vote/ConsensusBased.java:84
@SuppressWarnings({ "rawtypes", "unchecked" })
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int grant = 0;
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED -> grant++;
case AccessDecisionVoter.ACCESS_DENIED -> deny++;
default -> {
}
}
}
if (grant > deny) {
return;
}
if (deny > grant) {
throw new AccessDeniedException(
this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}
throw new AccessDeniedException(
this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
public boolean isAllowIfEqualGrantedDeniedDecisions() {
return this.allowIfEqualGrantedDeniedDecisions;
}
public void setAllowIfEqualGrantedDeniedDecisions(boolean allowIfEqualGrantedDeniedDecisions) {View on GitHub (pinned to 96852e8860)
Solutions
- Grant the missing authorities so grants >= denies
- Adjust which voters are registered or the attributes they evaluate
- Set allowIfEqualGrantedDeniedDecisions(true) only for tie cases (not this case, but review flag)
- Audit voter list for unintended duplicate or overly strict voters
Example fix
// before user authorities: [ROLE_USER]; access="ROLE_USER,ROLE_ADMIN" -> 1 deny, 1 grant // after user authorities: [ROLE_USER, ROLE_ADMIN] -> 2 grants, 0 denies
Defensive patterns
Strategy: validation
Validate before calling
long grants = voters.stream().filter(v -> v.vote(auth, obj, attrs) == AccessDecisionVoter.ACCESS_GRANTED).count();
long denies = voters.stream().filter(v -> v.vote(auth, obj, attrs) == AccessDecisionVoter.ACCESS_DENIED).count();
if (denies > grants) throw new AccessDeniedException("Would be denied by consensus"); Type guard
null
Try / catch
try {
consensusBased.decide(auth, object, attrs);
} catch (AccessDeniedException e) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Consensus denied");
} Prevention
- Remove duplicate voters that inflate deny counts
- Keep the set of securing attributes minimal and aligned to roles
- Test consensus outcomes for boundary vote distributions
When it happens
Trigger: An invocation where more voters voted ACCESS_DENIED than ACCESS_GRANTED, e.g. one affirmative, one deny, one abstain with allowIfEqualGrantedDeniedDecisions irrelevant or unequal case.
Common situations: Users partially matching role requirements across multiple voters; symmetric configurations where one more role is missing than present; duplicated voters amplifying denies.
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/de3e20a2ec77fb46.
Report an issue: GitHub.