skip to content

What is Spring Security's AccessDeniedHandler and when does it run?

level: juniorimportance: must knowfreq 45%

answer

  1. 403 for authenticated-but-unauthorized
  2. one method: handle(req,res,ex)
  3. called by ExceptionTranslationFilter
  4. default = AccessDeniedHandlerImpl
  5. 401 vs 403 split

basics

~10 s

It's the component that produces the HTTP 403 (Forbidden) response when a logged-in user tries to access something they're not allowed to. It runs after an AccessDeniedException is thrown.

solid answer

~30 s

AccessDeniedHandler is a Spring Security interface with one method, handle(request, response, AccessDeniedException). It is invoked by the ExceptionTranslationFilter when authorization fails for a request whose user is already authenticated — meaning they're logged in but lack the required role/authority. The default implementation, AccessDeniedHandlerImpl, sends a 403 Forbidden. This is distinct from authentication failure (not logged in), which yields 401 via a different component. You customize it to return a friendly error page or a JSON body instead of the default blank 403. Configure it through http.exceptionHandling(e -> e.accessDeniedHandler(...)).

code

java · 17 lines
java
// The interface you implement
public interface AccessDeniedHandler {
    void handle(HttpServletRequest request,
                HttpServletResponse response,
                AccessDeniedException accessDeniedException)
            throws IOException, ServletException;
}

// Registering the default page-based variant
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(a -> a
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated())
        .exceptionHandling(ex -> ex.accessDeniedPage("/error/403"));
    return http.build();
}

go deeper

for a junior

Know it produces 403 for an authenticated user who lacks permission, and that 401 is the separate not-logged-in case.

for a middle

Be able to name ExceptionTranslationFilter as the caller and configure a custom handler via exceptionHandling().

for a senior

Explain the anonymous-vs-authenticated routing done by AuthenticationTrustResolver and why anonymous denials become 401, not 403.

for a principal

Reason about where in the request lifecycle the exception is caught and how that determines whether your handler even runs versus MVC exception handling.

**The problem it solves.** In Spring Security there are two very different failure modes: (1) *authentication* failure — the caller is not logged in / has no valid credentials, which should produce **401 Unauthorized**; and (2) *authorization* failure — the caller **is** authenticated but is not permitted to perform this action, which should produce **403 Forbidden**. `AccessDeniedHandler` owns case (2). **The interface.** `org.springframework.security.web.access.AccessDeniedHandler` has a single method: ```java void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException; ``` Its job is to write the 403 response — status code, and optionally a body, headers, or a forward to an error page. **Who calls it.** The `ExceptionTranslationFilter` sits in the security filter chain. It wraps the rest of the chain in a try/catch. When a downstream authorization check (e.g. the `AuthorizationFilter`) throws an `AccessDeniedException`, the filter decides what to do: if the current authentication represents a **real, authenticated** user, it delegates to the `AccessDeniedHandler` (403). If the user is **anonymous** (or authenticated only via remember-me), it instead delegates to the `AuthenticationEntryPoint` to start authentication (401 / redirect to login). That anonymous-vs-authenticated decision is made by an `AuthenticationTrustResolver`. **The default.** If you configure nothing, `AccessDeniedHandlerImpl` runs. It calls `response.sendError(403)` (a blank server 403 page), or, if you set an error page via `accessDeniedPage(...)`, forwards the request there. **When to customize.** REST APIs almost always replace the default so a 403 returns a structured JSON error envelope instead of an HTML page. You provide a `@Component` implementing `AccessDeniedHandler` and register it. **Configuration (Spring Security 6, component-based DSL):** ```java http.exceptionHandling(ex -> ex.accessDeniedHandler(myAccessDeniedHandler)); ``` or the simpler page form: ```java http.exceptionHandling(ex -> ex.accessDeniedPage("/error/403")); ``` **Key terms.** *AccessDeniedException* — a `RuntimeException` in `org.springframework.security.access` thrown when an authorization decision denies access. *ExceptionTranslationFilter* — the filter that catches security exceptions and routes them to the handler or entry point. *AuthenticationEntryPoint* — the sibling component that handles the 401 case (out of scope here). **Common gotcha.** Candidates conflate 401 and 403. Remember: 401 = *who are you?* (not authenticated → AuthenticationEntryPoint); 403 = *I know who you are, you still can't* (authenticated but unauthorized → AccessDeniedHandler).

  • What is the difference between what AccessDeniedHandler and AuthenticationEntryPoint handle?
    AuthenticationEntryPoint handles the 401 case (request is not authenticated — start authentication), AccessDeniedHandler handles the 403 case (request is authenticated but lacks authority). ExceptionTranslationFilter picks between them based on whether the user is anonymous.
  • What HTTP status does the default AccessDeniedHandler return?
    403 Forbidden — via AccessDeniedHandlerImpl, which calls response.sendError(403) or forwards to a configured error page.

saying these in an interview costs you the question

  • Saying AccessDeniedHandler returns 401
  • Thinking it fires when the user is not logged in
  • Confusing it with AuthenticationEntryPoint
  • Believing it handles login/credential failures

context