spring-projects/spring-security · error · AccessDeniedException
Access is denied
Error message
Access is denied
What it means
UnanimousBased.decide() throws AccessDeniedException as soon as any single relevant voter votes ACCESS_DENIED. Unanimous strategy requires every voter with a matching attribute to grant; one deny aborts immediately.
Source
Thrown at access/src/main/java/org/springframework/security/access/vote/UnanimousBased.java:78
* @throws AccessDeniedException if access is denied
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes)
throws AccessDeniedException {
int grant = 0;
List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
singleAttributeList.add(null);
for (ConfigAttribute attribute : attributes) {
singleAttributeList.set(0, attribute);
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, singleAttributeList);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
throw new AccessDeniedException(this.messages
.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
default:
break;
}
}
}
// To get this far, there were no deny votes
if (grant > 0) {
return;
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
View on GitHub (pinned to 96852e8860)
Solutions
- Ensure the user holds ALL authorities referenced by the securing attributes
- Fix voter configuration/prefix so unintended denies become abstentions
- Switch to AffirmativeBased if any-grant semantics are desired
- Enable debug logging for AbstractAccessDecisionManager to find the denying voter
Example fix
// before <intercept-url pattern="/**" access="ROLE_A,ROLE_B"/> // UnanimousBased: user with only ROLE_A is denied by the ROLE_B attribute // after <intercept-url pattern="/**" access="ROLE_A or ROLE_B"/> // or grant both roles
Defensive patterns
Strategy: try-catch
Validate before calling
boolean unanimous(List<AccessDecisionVoter<?>> voters, Authentication a, Object o, List<ConfigAttribute> attrs) {
return attrs.stream().allMatch(attr -> voters.stream()
.filter(v -> v.supports(attr))
.allMatch(v -> v.vote(a, o, List.of(attr)) != AccessDecisionVoter.ACCESS_DENIED));
} Type guard
null
Try / catch
try {
unanimousBased.decide(auth, object, attrs);
} catch (AccessDeniedException e) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "A required authority is missing");
} Prevention
- Ensure users hold every authority listed in multi-attribute rules
- Prefer expression-based @PreAuthorize with 'or' semantics where any-of is intended
- Enable voter debug logging to identify the single denying voter
When it happens
Trigger: Any voter returns ACCESS_DENIED for its matching config attribute during the per-attribute voting loop (each attribute is voted individually with a single-attribute list).
Common situations: User missing one of several roles required by a multi-attribute rule; prefix mismatch making RoleVoter deny unexpectedly; migrating from AffirmativeBased where a single grant masked the deny.
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/364e5f072f1f88cd.
Report an issue: GitHub.