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?
answer
- saveRequest before commence
- HttpSessionRequestCache = default; NullRequestCache for APIs
- SavedRequestAwareAuthenticationSuccessHandler returns you back
- mixed app → multiple SecurityFilterChain beans
- DelegatingAuthenticationEntryPoint routes by matcher
basics
~20 sBefore 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 sWhen 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// 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
Know the request is saved before login and restored after, so you land back on the original page.
Name RequestCache/HttpSessionRequestCache and SavedRequestAwareAuthenticationSuccessHandler as the two halves.
Configure NullRequestCache and a 401 entry point for API paths so the mechanism doesn't misfire.
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)