skip to content

Beyond authentication, how do you audit authorization decisions with AuthorizationDeniedEvent, and how does that differ from authentication events?

level: principalimportance: should knowfreq 25%

answer

  1. authN = who are you; authZ = may you do this
  2. AuthorizationDeniedEvent / AuthorizationGrantedEvent
  3. must register SpringAuthorizationEventPublisher bean (opt-in)
  4. getAuthentication() is a Supplier -> .get()
  5. Granted fires per-decision -> high volume, audit denials

basics

~10 s

Spring Security 6 can publish AuthorizationGrantedEvent and AuthorizationDeniedEvent for access-control decisions. Register a SpringAuthorizationEventPublisher bean, then use @EventListener(AuthorizationDeniedEvent.class) to audit denied access — separate from login success/failure events.

solid answer

~40 s

Authentication events cover *login* outcomes; authorization events cover *access-control* outcomes made by the `AuthorizationManager` (method security and request authorization). In Spring Security 6 these are published through an `AuthorizationEventPublisher`, but unlike authentication events they are **not** wired by default — you must register a `SpringAuthorizationEventPublisher` bean. Then `@EventListener` on `AuthorizationDeniedEvent` lets you audit denied access: it carries a `Supplier<Authentication>`, the secured object, and the `AuthorizationDecision`. `AuthorizationGrantedEvent` also exists but fires on *every* granted access, so it's high-volume — you typically filter it (SpEL condition or logic) or ignore it and audit only denials. Use denial events for security monitoring and intrusion detection, complementing `AbstractAuthenticationFailureEvent` which only sees failed logins, not authorized-but-forbidden actions.

code

java · 25 lines
java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.security.authorization.AuthorizationDeniedEvent;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.stereotype.Component;

@Configuration
class AuthzEventsConfig {
    // Authorization events are OPT-IN: register the publisher explicitly.
    @Bean
    AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher pub) {
        return new SpringAuthorizationEventPublisher(pub);
    }
}

@Component
class AccessDeniedAuditListener {
    @EventListener
    void onDenied(AuthorizationDeniedEvent<?> event) {
        var who = event.getAuthentication().get();     // Supplier<Authentication>
        var result = event.getAuthorizationResult();   // the denied decision
        // persist an audit record: `who` denied on `event.getSource()`
    }
}

go deeper

for a junior

Know authorization (allowed?) differs from authentication (who?).

for a middle

Know AuthorizationDeniedEvent exists and is consumed via @EventListener.

for a senior

Know it's opt-in via SpringAuthorizationEventPublisher and why granted events are high-volume.

for a principal

Design a unified security audit combining authentication-failure and authorization-denied events, filtering grants and handling the Supplier/AuthorizationResult API and version differences.

## Two different decision points - **Authentication events** (`AuthenticationSuccessEvent`, `AbstractAuthenticationFailureEvent`) answer *"is this who they say they are?"* — fired by the `AuthenticationManager`. - **Authorization events** (`AuthorizationGrantedEvent`, `AuthorizationDeniedEvent`) answer *"is this authenticated principal allowed to do X?"* — fired around `AuthorizationManager.check(...)` for method security (`@PreAuthorize`, `@PostAuthorize`) and request-level authorization. An already-logged-in user hitting a forbidden endpoint produces an **AuthorizationDeniedEvent**, not an authentication failure event — so authentication auditing alone misses these. ## Enabling authorization events (they are opt-in) Unlike the authentication publisher, the authorization publisher is **not** auto-wired. You register it: ```java @Bean AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher pub) { return new SpringAuthorizationEventPublisher(pub); } ``` With `@EnableMethodSecurity`, the method-security interceptors pick up this `AuthorizationEventPublisher` bean and publish events on each decision. ## Consuming denials ```java @Component class AccessDeniedAuditListener { @EventListener void onDenied(AuthorizationDeniedEvent<?> event) { Authentication who = event.getAuthentication().get(); Object resource = event.getSource(); // the secured object / invocation AuthorizationResult result = event.getAuthorizationResult(); // audit: who was denied access to resource } } ``` `AuthorizationDeniedEvent` exposes: `getAuthentication()` (a `Supplier<Authentication>` — call `.get()`), `getSource()`/the secured object (e.g. the `MethodInvocation`), and the authorization result/decision. (In 6.0 the type was `AuthorizationDecision`; from 6.3 it generalized to `AuthorizationResult`.) ## The AuthorizationGrantedEvent volume trap `AuthorizationGrantedEvent` fires on **every** granted decision — potentially thousands per request across nested method calls. Publishing and handling all of them is expensive. Best practice: audit **only** `AuthorizationDeniedEvent`, or if you need granted events, use `@EventListener`'s `condition` SpEL or a custom `AuthorizationEventPublisher` that filters which grants are published (e.g. only for sensitive annotations). Naively logging all grants is a performance and log-noise footgun. ## Comparison table | Aspect | Authentication events | Authorization events | |---|---|---| | Question | Who are you? | May you do this? | | Fired by | AuthenticationManager (ProviderManager) | AuthorizationManager / method interceptors | | Default wired? | Yes (Boot autoconfig) | No — register SpringAuthorizationEventPublisher | | Denial event | AbstractAuthenticationFailureEvent subtypes | AuthorizationDeniedEvent | | Success/grant event | AuthenticationSuccessEvent | AuthorizationGrantedEvent (high volume) | ## Gotchas - Forgetting the `SpringAuthorizationEventPublisher` bean → no authorization events at all (silent). - Listening for `AuthorizationGrantedEvent` unfiltered → performance and log-flood problems. - The `getAuthentication()` is a `Supplier` — you must call `.get()`; it may resolve lazily. - Denial via the filter chain that returns 403 through `AccessDeniedHandler` is a related but distinct mechanism; method-security denials are what `AuthorizationDeniedEvent` most reliably captures. Don't assume every 403 in the app produces the event unless that path goes through an `AuthorizationManager` with the publisher wired. - These are Spring Security 6 APIs; older (5.x) code used `AuthorizedEvent`/voter-based `AuthorizationFailureEvent` from the legacy `AbstractSecurityInterceptor`, which differ. ## When to use Use `AuthorizationDeniedEvent` auditing for security monitoring — detecting privilege-probing, building an access-denied trail for compliance, and feeding intrusion-detection. Combine with authentication failure events for a complete security audit: failed logins *and* forbidden actions by authenticated users.

  • Why is auditing AuthorizationGrantedEvent risky?
    It fires on every granted authorization decision — potentially thousands per request across nested secured calls — so handling all of them causes performance overhead and log flooding. Audit denials, or filter grants tightly.
  • An authenticated user hits a @PreAuthorize they lack the role for. Which event fires — a failure authentication event or something else?
    AuthorizationDeniedEvent (an authorization event), provided a SpringAuthorizationEventPublisher is registered. Authentication succeeded; only authorization was denied, so no AbstractAuthenticationFailureEvent is fired.
  • Why might you see no authorization events at all despite denied access?
    Authorization events are opt-in — without a SpringAuthorizationEventPublisher bean, the AuthorizationManager has no publisher and emits nothing.

saying these in an interview costs you the question

  • Thinking authorization events are auto-published like authentication events
  • Auditing AuthorizationGrantedEvent unfiltered in production
  • Expecting a forbidden action by an authenticated user to fire AbstractAuthenticationFailureEvent
  • Calling getAuthentication() as if it returns an Authentication directly instead of a Supplier

context