skip to content

What is the difference between logoutSuccessUrl and LogoutSuccessHandler, and how do you make logout REST/SPA friendly?

level: middleimportance: should knowfreq 38%

answer

  1. logoutSuccessUrl = redirect target (302)
  2. LogoutSuccessHandler = full response strategy
  3. handler overrides url
  4. HttpStatusReturningLogoutSuccessHandler -> 200 for SPA
  5. runs last, exactly one

basics

~10 s

logoutSuccessUrl sets the redirect target after logout. LogoutSuccessHandler is the full strategy that produces the response. For a REST/SPA client you replace the redirect with HttpStatusReturningLogoutSuccessHandler so logout returns 200 instead of a 302.

solid answer

~40 s

After all LogoutHandlers run, the LogoutFilter delegates to a LogoutSuccessHandler to write the response. The default is SimpleUrlLogoutSuccessHandler, which sends a 302 redirect; logoutSuccessUrl(...) just sets that redirect's target (default /login?logout). LogoutSuccessHandler is the extension point when a redirect isn't what you want — for a single-page app or REST API you don't want a 302 to an HTML page, you want a status code. Spring provides HttpStatusReturningLogoutSuccessHandler, which returns a bare 200 (or a status you choose). Note that calling logoutSuccessHandler(...) overrides logoutSuccessUrl(...) — you use one or the other. You can also implement a custom handler to write JSON, log audit data, or vary the response by Accept header.

code

java · 13 lines
java
// Server-rendered app: redirect after logout
http.logout(l -> l.logoutSuccessUrl("/login?logout"));

// SPA / REST: return 200 instead of a redirect
http.logout(l -> l
    .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()));

// Custom JSON response (also fine for auditing)
http.logout(l -> l.logoutSuccessHandler((req, res, auth) -> {
    res.setStatus(200);
    res.setContentType("application/json");
    res.getWriter().write("{\"status\":\"ok\"}");
}));

go deeper

for a junior

Know logoutSuccessUrl sets the post-logout redirect.

for a middle

Distinguish the URL knob from the full handler and know the SPA-friendly HttpStatusReturningLogoutSuccessHandler.

for a senior

Discuss content-negotiated responses and the handler-overrides-url rule.

for a principal

Weigh auditing via events vs handlers and response design for mixed browser/API clients.

## Two levels of customization After the logout handlers have cleaned up auth state, *something* has to tell the browser/client what happened. That is the **`LogoutSuccessHandler`** interface: ```java void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication); ``` ### 1. logoutSuccessUrl — the simple knob `.logoutSuccessUrl("/login?logout")` configures the **default** handler, `SimpleUrlLogoutSuccessHandler`, to redirect (HTTP 302) to that URL. This is perfect for a **server-rendered** (MVC/Thymeleaf) app where the browser follows redirects and shows a login page. ### 2. logoutSuccessHandler — the full strategy `.logoutSuccessHandler(myHandler)` replaces the entire strategy. **Important:** setting a handler makes `logoutSuccessUrl` irrelevant — the handler is fully in control. Use this when a redirect is wrong. ## REST / SPA logout A single-page app calls `/logout` via `fetch`/XHR and does its own client-side navigation. A 302 redirect to an HTML login page is useless (and often followed transparently by the browser, returning HTML the JS didn't expect). The idiomatic fix is Spring's built-in: ```java http.logout(logout -> logout .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()) // -> 200 OK ); ``` `HttpStatusReturningLogoutSuccessHandler` writes a bare status code (default `200 OK`; pass a `HttpStatus` to choose another, e.g. `NO_CONTENT`). No body, no redirect — exactly what a SPA wants. ## Custom handler For JSON responses or auditing: ```java LogoutSuccessHandler json = (req, res, auth) -> { res.setStatus(HttpServletResponse.SC_OK); res.setContentType("application/json"); res.getWriter().write("{\"status\":\"logged_out\"}"); }; ``` You can also inspect the `Accept` header and redirect for browsers but return 200 for API clients. ## Success handler vs LogoutHandler — don't confuse them - **`LogoutHandler`** (e.g. `SecurityContextLogoutHandler`) — *does the cleanup*, runs first, can be many (composite). - **`LogoutSuccessHandler`** — *writes the final response*, runs last, exactly one. ## Events A successful logout also fires a `LogoutSuccessEvent` (via `LogoutSuccessEventPublishingLogoutHandler`); you can listen with `@EventListener` for auditing without touching the success handler. ## Gotchas - Configuring both `logoutSuccessUrl` and `logoutSuccessHandler` — the handler wins; the URL is silently ignored. - A SPA still seeing a redirect after logout usually means the default handler is still in place — you forgot to swap it. - The success handler runs *after* session invalidation, so `authentication` may be the pre-logout principal but the session is already gone.

  • If you set both logoutSuccessUrl and logoutSuccessHandler, which one takes effect?
    The LogoutSuccessHandler. Providing a handler replaces the default SimpleUrlLogoutSuccessHandler entirely, so logoutSuccessUrl is ignored. Use one or the other, not both.
  • How would you audit every logout without changing the response?
    Listen for the LogoutSuccessEvent published by LogoutSuccessEventPublishingLogoutHandler using an @EventListener, or add a custom LogoutHandler that logs. Both keep the success-handler/response behavior untouched.

saying these in an interview costs you the question

  • Thinking logoutSuccessUrl still applies after you set a custom LogoutSuccessHandler.
  • Confusing LogoutHandler (cleanup) with LogoutSuccessHandler (response).
  • Returning a 302 redirect to a SPA and expecting the JS client to handle it well.

context