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

  1. Grant the user the missing role/authority the denying voter requires
  2. Remove or adjust the voter/attribute that inappropriately denies
  3. Switch strategy to ConsensusBased or UnanimousBased if a single deny should not block
  4. 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

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.