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

  1. Ensure the user holds ALL authorities referenced by the securing attributes
  2. Fix voter configuration/prefix so unintended denies become abstentions
  3. Switch to AffirmativeBased if any-grant semantics are desired
  4. 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

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.