Explain AuthorizationDecision and how abstaining works when composing AuthorizationManagers with allOf/anyOf.
answer
- AuthorizationDecision.isGranted() — allow/deny
- null = abstain, false = deny (different!)
- anyOf = OR (first grant wins), allOf = AND (any deny vetoes)
- not() inverts; all-abstain in allOf → abstain
- ExpressionAuthorizationDecision carries the SpEL
basics
~10 sAuthorizationDecision wraps a boolean 'granted' result. An AuthorizationManager can also return null to abstain (no opinion). AuthorizationManagers.anyOf grants if any manager grants; allOf grants only if all grant and none deny.
solid answer
~40 sAuthorizationDecision is the concrete result an AuthorizationManager returns; isGranted() tells the caller allow or deny. A subclass, ExpressionAuthorizationDecision, additionally carries the SpEL expression that produced the result for diagnostics. Crucially, a manager may return null instead of a decision — that's an *abstain*, meaning 'no opinion', distinct from an explicit deny. The AuthorizationManagers combinators use this: anyOf(...) returns granted as soon as any delegate grants, otherwise denies (abstains are skipped); allOf(...) grants only if every delegate that expresses an opinion grants and none denies — if all abstain it can abstain too. not(...) inverts a decision. This lets you build layered rules where some managers stay silent. In modern versions (6.4+), authorize() returns AuthorizationResult, of which AuthorizationDecision is the primary implementation, keeping the same granted semantics.
code
java · 27 linesimport static org.springframework.security.authorization.AuthorizationManagers.*;
// Grant if ADMIN, OR (fully authenticated AND passes a custom owner check)
AuthorizationManager<RequestAuthorizationContext> rule = anyOf(
AuthorityAuthorizationManager.hasRole("ADMIN"),
allOf(
AuthenticatedAuthorizationManager.fullyAuthenticated(),
new ResourceOwnerAuthorizationManager() // custom
)
);
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/docs/**").access(rule)
.anyRequest().denyAll());
// A custom manager returning abstain (null) vs deny
class ResourceOwnerAuthorizationManager
implements AuthorizationManager<RequestAuthorizationContext> {
@Override
public AuthorizationDecision check(Supplier<Authentication> auth,
RequestAuthorizationContext ctx) {
String id = ctx.getVariables().get("id"); // from path var
if (id == null) return null; // abstain: not my concern
boolean owns = id.equals(auth.get().getName());
return new AuthorizationDecision(owns); // grant or deny
}
}go deeper
Know AuthorizationDecision holds a granted boolean.
Explain anyOf vs allOf vs not and the granted/denied outcome.
Distinguish abstain (null) from deny and reason about combinator short-circuiting.
Discuss the AuthorizationResult evolution, ExpressionAuthorizationDecision diagnostics, and how abstain interacts with deny-by-default dispatch.
## AuthorizationDecision `AuthorizationDecision` is the value object returned by `AuthorizationManager.check(...)`. Its key API is `boolean isGranted()`. Construct it with `new AuthorizationDecision(true)` (allow) or `new AuthorizationDecision(false)` (deny). A notable subclass is `ExpressionAuthorizationDecision`, produced by `WebExpressionAuthorizationManager` and SpEL-based method security. It records the `EvaluationExpression` that yielded the result, which powers better error messages and observability. In Spring Security 6.4 the SPI method evolved from `check(...)` (returning `AuthorizationDecision`) to `authorize(...)` returning the broader `AuthorizationResult` interface; `AuthorizationDecision implements AuthorizationResult`, so existing code and semantics carry over. ## Abstain: null vs deny Returning **`null`** from a manager means **abstain** — 'I have no opinion, let someone else decide'. This is semantically different from returning `new AuthorizationDecision(false)` (an explicit **deny**). The distinction only matters when managers are combined; a top-level manager returning null at the `AuthorizationFilter` is generally treated as not-granted. ## Combinators — AuthorizationManagers The `AuthorizationManagers` utility composes managers: - **`anyOf(AuthorizationManager...)`** — OR semantics. Iterates delegates; the first that returns a *granted* decision wins (grant). Abstains (null) are ignored. If none grant, the result is deny. - **`allOf(AuthorizationManager...)`** — AND semantics. Grants only if no delegate denies. A deny short-circuits to deny. Abstains are tolerated; if every delegate abstains, the combined manager also abstains (returns null) rather than granting — so you can't accidentally grant from pure silence. - **`not(AuthorizationManager)`** — inverts: a grant becomes deny and vice-versa. These compose recursively, so you can express, e.g., "(ROLE_ADMIN) OR (fully-authenticated AND owns-the-resource)". ## Why abstain matters In `RequestMatcherDelegatingAuthorizationManager` (the web dispatcher), each request matcher maps to one manager. If no matcher matches, the delegating manager returns null (abstain) — and since 6.0's deny-by-default, an unmatched request that reaches the end is denied. So leaving requests unmapped is effectively a deny, and Spring will also fail fast if you forget `anyRequest()`. ## Gotchas - Don't confuse abstain (null) with deny (false) when writing a custom combinator or manager — returning null inside `allOf` is 'no opinion', returning false is a hard veto. - `isGranted()` false is terminal in `allOf`: no later manager can rescue it. - Custom managers should decide deterministically; returning null when you actually mean deny opens holes if the manager is later wrapped in `anyOf`.
- Inside AuthorizationManagers.allOf, what happens if one manager denies and another grants?The deny wins — allOf grants only when no delegate denies, so a single false (deny) short-circuits the whole thing to deny.
- What is ExpressionAuthorizationDecision and why does it exist?A subclass of AuthorizationDecision returned by SpEL-based managers; it carries the evaluated expression so error messages and observability can report which rule produced the result.
saying these in an interview costs you the question
- Treating null (abstain) and AuthorizationDecision(false) (deny) as interchangeable.
- Saying allOf grants when all managers abstain — it abstains instead.
- Believing anyOf needs all delegates to grant (that's allOf).