skip to content

How does ExceptionTranslationFilter enable 'redirect back to the originally requested page after login', and how would you tune this in a mixed API/browser system?

level: principalimportance: should knowfreq 25%

answer

  1. saveRequest before commence
  2. HttpSessionRequestCache = default; NullRequestCache for APIs
  3. SavedRequestAwareAuthenticationSuccessHandler returns you back
  4. mixed app → multiple SecurityFilterChain beans
  5. DelegatingAuthenticationEntryPoint routes by matcher

basics

~20 s

Before commencing the entry point, ExceptionTranslationFilter saves the current request in a RequestCache. After the user logs in, a SavedRequestAware success handler pulls that saved request out and redirects them back to where they were originally going.

solid answer

~40 s

When ExceptionTranslationFilter commences authentication it first stores the current request as a `SavedRequest` in the configured `RequestCache` (default `HttpSessionRequestCache`, keyed in the session). After a successful login, `SavedRequestAwareAuthenticationSuccessHandler` looks up that `SavedRequest` and redirects the user back to the original URL instead of a fixed default. In a mixed system you must be careful: for `/api/**` you don't want request caching or redirects at all, so you configure a `DelegatingAuthenticationEntryPoint` (401 for API, login redirect for browser) and typically a `NullRequestCache` or a `RequestCacheAwareFilter` scoped away from API paths — otherwise you leak session state and produce nonsensical post-login redirects for programmatic clients. You also decide whether the entry point should be per-chain (multiple `SecurityFilterChain` beans matched by path) rather than one delegating entry point — often cleaner in practice.

code

java · 13 lines
java
// Browser chain: default saved-request return-trip works out of the box.
// Pure API chain: disable it so nothing touches the session.
@Bean @Order(1)
SecurityFilterChain api(HttpSecurity http) throws Exception {
    http.securityMatcher("/api/**")
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .requestCache(RequestCacheConfigurer::disable)      // NullRequestCache
        .exceptionHandling(e -> e.authenticationEntryPoint(
            new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
        .authorizeHttpRequests(a -> a.anyRequest().authenticated())
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

go deeper

for a junior

Know the request is saved before login and restored after, so you land back on the original page.

for a middle

Name RequestCache/HttpSessionRequestCache and SavedRequestAwareAuthenticationSuccessHandler as the two halves.

for a senior

Configure NullRequestCache and a 401 entry point for API paths so the mechanism doesn't misfire.

for a principal

Choose between multiple filter chains vs DelegatingAuthenticationEntryPoint and reason about open-redirect, session-fixation, and CSRF/replay risks of saved requests at scale.

## The post-login return-trip mechanism A very common UX: an unauthenticated user clicks `/orders/42`, is bounced to `/login`, logs in, and lands **back on `/orders/42`**. Two collaborating pieces make this work, and `ExceptionTranslationFilter` is where it starts. ### 1. Saving the request (ExceptionTranslationFilter) Inside `sendStartAuthentication`, before calling the entry point, the filter does: ```java this.requestCache.saveRequest(request, response); ``` - `RequestCache` is a strategy; the default is `HttpSessionRequestCache`, which serializes a `DefaultSavedRequest` (URL, method, headers, parameters) into the HTTP session under a well-known key. - Then it clears the `SecurityContext` and calls `authenticationEntryPoint.commence(...)`. ### 2. Restoring the request (on successful login) `SavedRequestAwareAuthenticationSuccessHandler` (the default success handler for form login) checks the `RequestCache` for a `SavedRequest`. If present, it redirects to that saved URL; if absent, it falls back to the configured default target URL (e.g. `/`). A companion `RequestCacheAwareFilter` can even replay the original request transparently. ## Why this is a problem in mixed API/browser apps For a REST client, none of this makes sense: - There is no interactive login step to "return" from. - Saving requests in the session couples a stateless API to session state. - If the API entry point mistakenly redirects, the client follows it and the saved-request machinery produces confusing behaviour. ### Design options **Option A — Multiple `SecurityFilterChain` beans (preferred).** Define a chain with `securityMatcher("/api/**")` that is stateless (`SessionCreationPolicy.STATELESS`), uses a `401` entry point, and a `NullRequestCache`; and a separate chain for the browser UI with form login, `HttpSessionRequestCache`, and a login-redirect entry point. Order them with `@Order`. This keeps each concern clean and avoids per-request branching. ```java @Bean @Order(1) SecurityFilterChain api(HttpSecurity http) throws Exception { http.securityMatcher("/api/**") .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .exceptionHandling(e -> e.authenticationEntryPoint( new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) .requestCache(RequestCacheConfigurer::disable) .authorizeHttpRequests(a -> a.anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()); return http.build(); } @Bean @Order(2) SecurityFilterChain web(HttpSecurity http) throws Exception { http.authorizeHttpRequests(a -> a.anyRequest().authenticated()) .formLogin(Customizer.withDefaults()); // saved-request redirect works here return http.build(); } ``` **Option B — Single chain with `DelegatingAuthenticationEntryPoint`.** One chain, one entry point that routes by `RequestMatcher` (401 for API, login redirect otherwise). Simpler wiring but you must still tame the RequestCache (e.g. a matcher-scoped `HttpSessionRequestCache` or `NullRequestCache`) so API paths don't leak session state. ## Gotchas at scale - **Open redirect / cache poisoning**: SavedRequest replays the originally requested URL; ensure your login flow can't be tricked into saving an off-site URL. Spring's defaults guard the typical cases, but custom success handlers can reintroduce risk. - **Session fixation**: saving requests implies a session; combine with Spring's session-fixation protection (on by default) so the pre-login session isn't trusted post-login. - **CSRF + saved POST**: a saved non-GET request replayed after login needs a fresh CSRF token; naively replaying a POST can fail or surprise. - **`NullRequestCache`**: use it for pure APIs to guarantee nothing is stored. ## When to reach for what Browser-only app: defaults are perfect — leave RequestCache and the SavedRequestAware handler alone. Mixed system: prefer multiple filter chains (Option A) for clarity; use DelegatingAuthenticationEntryPoint only when a single chain is truly required.

  • Which handler performs the actual post-login redirect back to the original page, and where does it get the target?
    SavedRequestAwareAuthenticationSuccessHandler; it reads the SavedRequest from the RequestCache (default HttpSessionRequestCache) and redirects there, falling back to a default URL if none exists.
  • Why disable the RequestCache (NullRequestCache) on a stateless API chain?
    There's no interactive login to return from, and saving requests couples a stateless API to the HTTP session and can produce nonsensical redirects; NullRequestCache guarantees nothing is stored.
  • What security risks accompany replaying a SavedRequest after login?
    Open-redirect if an attacker-controlled URL is saved, session-fixation if the pre-login session is trusted, and CSRF/replay issues when replaying a saved POST without a fresh token.

saying these in an interview costs you the question

  • Enabling saved-request redirects on a stateless API and expecting sensible client behaviour
  • Assuming a single entry point can serve both API and browser without also taming the RequestCache
  • Not recognizing that the return-trip requires a session (HttpSessionRequestCache)

context