How do you write a custom AuthorizationManager and plug it into method security or HTTP authorization?
answer
- Implement AuthorizationManager<RequestAuthorizationContext> or <MethodInvocation>
- .access(manager) for HTTP
- AuthorizationManagerBeforeMethodInterceptor for methods
- Only call Supplier.get() when needed
- Compose with AuthorizationManagers.allOf/anyOf; reactive = ReactiveAuthorizationManager<Mono>
basics
~10 sImplement AuthorizationManager<T> (T = RequestAuthorizationContext for web or MethodInvocation for methods) and return an AuthorizationDecision. Wire it via .access(manager) in authorizeHttpRequests, or register an AuthorizationManagerBeforeMethodInterceptor for method security.
solid answer
~30 sFor HTTP, implement AuthorizationManager<RequestAuthorizationContext>, read what you need from the request/URI variables plus the supplied Authentication, and return new AuthorizationDecision(granted). Register it with .requestMatchers(...).access(yourManager). For method security, implement AuthorizationManager<MethodInvocation> (or MethodInvocationResult for post-authorization) and expose an AuthorizationManagerBeforeMethodInterceptor / AuthorizationManagerAfterMethodInterceptor bean, ordered with the built-in @PreAuthorize/@PostAuthorize interceptors. Key rules: call the Supplier lazily and only when needed; never call .get() on an anonymous request unless you handle AuthenticationCredentialsNotFoundException; keep the decision deterministic and side-effect-free; return null only if you genuinely mean abstain. You can also compose your manager with built-ins via AuthorizationManagers.anyOf/allOf instead of reimplementing role checks.
code
java · 29 linespublic class OrderOwnerAuthorizationManager
implements AuthorizationManager<RequestAuthorizationContext> {
private final OrderRepository orders;
public OrderOwnerAuthorizationManager(OrderRepository orders) {
this.orders = orders;
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
RequestAuthorizationContext ctx) {
String orderId = ctx.getVariables().get("id"); // from /orders/{id}
if (orderId == null) {
return null; // abstain: this manager doesn't apply here
}
Authentication auth = authentication.get(); // resolve only now
boolean owns = orders.findById(orderId)
.map(o -> o.getOwner().equals(auth.getName()))
.orElse(false);
return new AuthorizationDecision(owns);
}
}
// Wiring:
// http.authorizeHttpRequests(a -> a
// .requestMatchers("/orders/{id}")
// .access(new OrderOwnerAuthorizationManager(orderRepository))
// .anyRequest().authenticated());go deeper
Know you implement the interface and return an AuthorizationDecision.
Pick the right T and wire via .access(); understand lazy Authentication.
Register method-security interceptors with correct ordering and compose with built-ins; handle anonymous safely.
Weigh per-request cost, caching, tenant isolation patterns, reactive variants, and abstain-vs-deny correctness across combinators.
## The shape of a custom manager ```java public interface AuthorizationManager<T> { AuthorizationDecision check(Supplier<Authentication> authentication, T object); } ``` You choose `T` based on where it plugs in: - **Web:** `RequestAuthorizationContext` — gives you the `HttpServletRequest` and captured URI template variables (`getVariables()`), handy for ownership checks like `/orders/{id}`. - **Method (pre):** `MethodInvocation` — the intercepted call, its arguments and target. - **Method (post):** `MethodInvocationResult` — the invocation plus the returned value, for `@PostAuthorize`-style filtering on results. ## Wiring into HTTP ```java http.authorizeHttpRequests(auth -> auth .requestMatchers("/orders/{id}").access(new OrderOwnerAuthorizationManager()) .anyRequest().authenticated()); ``` `.access(AuthorizationManager)` is the general-purpose hook; the DSL's `hasRole`/`authenticated` are just conveniences that build managers for you. ## Wiring into method security With `@EnableMethodSecurity`, `@PreAuthorize` is enforced by `AuthorizationManagerBeforeMethodInterceptor` wrapping a `PreAuthorizeAuthorizationManager`; `@PostAuthorize` by `AuthorizationManagerAfterMethodInterceptor`. To add your own rule globally: ```java @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) AuthorizationManagerBeforeMethodInterceptor customMethodSecurity() { AuthorizationManager<MethodInvocation> manager = new MyMethodAuthorizationManager(); AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(Pointcut.TRUE, manager); interceptor.setOrder(/* relative to AuthorizationInterceptorsOrder.PRE_AUTHORIZE */); return interceptor; } ``` Use `AuthorizationInterceptorsOrder` to position relative to the built-ins. ## Rules and gotchas 1. **Lazy Authentication.** The `Supplier<Authentication>` defers resolution. Only call `.get()` when your logic needs the principal; calling it for an anonymous/unauthenticated request throws `AuthenticationCredentialsNotFoundException`. Note that even the anonymous user has an `Authentication` (`AnonymousAuthenticationToken`) once the anonymous filter runs, but relying on that is fragile. 2. **Abstain vs deny.** Return `null` only to abstain (typically when your matcher shouldn't apply). Return `new AuthorizationDecision(false)` to actively deny. 3. **No side effects.** Managers may be called for every request; keep them fast and pure. Don't mutate state or perform expensive I/O without caching. 4. **Compose, don't reinvent.** Prefer `AuthorizationManagers.allOf(AuthenticatedAuthorizationManager.authenticated(), myManager)` over re-checking authentication yourself. 5. **Throwing to deny.** Throwing `AccessDeniedException` also denies, but returning a deny decision is cleaner and lets combinators reason about it. 6. **Reactive apps** use `ReactiveAuthorizationManager<T>` whose `check` returns `Mono<AuthorizationDecision>` — same idea, non-blocking. ## When to use Reach for a custom manager when a rule can't be expressed as a static role/authority or a simple SpEL expression — e.g. resource ownership, tenant isolation, time-of-day windows, or checks needing a repository lookup.
- Your custom manager calls authentication.get() but the endpoint is also reachable anonymously. What can go wrong?get() throws AuthenticationCredentialsNotFoundException if there's no authenticated principal. Guard the path with authenticated() first, combine via allOf, or only call get() after confirming the request requires auth.
- How would the same logic differ in a WebFlux app?Implement ReactiveAuthorizationManager<T> whose check returns Mono<AuthorizationDecision>, keeping the repository lookup non-blocking; wire it via the reactive authorizeExchange DSL's .access(...).
saying these in an interview costs you the question
- Performing expensive I/O per request without caching in a manager that runs on every call.
- Returning null when the intent is to deny, creating a hole under anyOf.
- Eagerly calling authentication.get() and crashing on anonymous requests.
- Reinventing role checks instead of composing with AuthorityAuthorizationManager.