skip to content

Trace what happens inside UsernamePasswordAuthenticationFilter from form submission to a persisted authenticated session.

level: middleimportance: should knowfreq 55%

answer

  1. attemptAuthentication reads username/password -> unauth token
  2. ProviderManager -> DaoAuthenticationProvider -> UserDetailsService + PasswordEncoder
  3. success: session-fixation, save via SecurityContextRepository (session)
  4. same UsernamePasswordAuthenticationToken class, isAuthenticated flips
  5. later requests restored by SecurityContextHolderFilter

basics

~20 s

The filter reads username/password from the POST, builds an unauthenticated token, and calls the AuthenticationManager. If it returns an authenticated token, the filter saves it to the SecurityContext (and the session) and runs the success handler; otherwise it runs the failure handler.

solid answer

~40 s

On a matching POST /login, UsernamePasswordAuthenticationFilter.attemptAuthentication reads the username and password parameters and creates an unauthenticated UsernamePasswordAuthenticationToken. It calls AuthenticationManager.authenticate — typically a ProviderManager delegating to DaoAuthenticationProvider, which loads the user via UserDetailsService and verifies the password with PasswordEncoder. On success the manager returns a fully authenticated token with authorities. The filter (via its base class) then: applies session-fixation protection (new session id), creates a fresh SecurityContext, sets the authentication on it, and persists it through the SecurityContextRepository (HttpSessionSecurityContextRepository by default, so it lands in the session), publishes an InteractiveAuthenticationSuccessEvent, and calls the AuthenticationSuccessHandler. On an AuthenticationException it clears the context and calls the AuthenticationFailureHandler. Later requests are restored from the session by SecurityContextHolderFilter/SecurityContextPersistenceFilter — no re-authentication needed.

code

java · 15 lines
java
// Conceptual sketch of the filter's core (simplified from Spring source)
public Authentication attemptAuthentication(HttpServletRequest req,
                                            HttpServletResponse res) {
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    UsernamePasswordAuthenticationToken unauth =
        UsernamePasswordAuthenticationToken.unauthenticated(username, password);
    unauth.setDetails(authenticationDetailsSource.buildDetails(req));
    return this.getAuthenticationManager().authenticate(unauth); // -> ProviderManager
}
// On success, the base class saves context + runs the success handler:
//   SecurityContext ctx = securityContextHolderStrategy.createEmptyContext();
//   ctx.setAuthentication(authResult);
//   securityContextRepository.saveContext(ctx, req, res); // -> session
//   successHandler.onAuthenticationSuccess(req, res, authResult);

go deeper

for a junior

Know the filter checks the password via a manager and, if OK, logs the user in for the session.

for a middle

Trace token creation, ProviderManager/DaoAuthenticationProvider, UserDetailsService+PasswordEncoder, and context persistence.

for a senior

Discuss session-fixation protection, the SS6 explicit save model, and SecurityContextHolderFilter restoring context.

for a principal

Reason about extension points (custom providers/details), stateless variants, and pitfalls of manual context manipulation.

## The end-to-end flow Assume `formLogin()` is enabled and the user submits the login form (`POST /login`). ### 1. Filter match `UsernamePasswordAuthenticationFilter` (a subclass of `AbstractAuthenticationProcessingFilter`) checks its `RequestMatcher` (default `POST /login`). If it matches, it handles the request; otherwise it passes the request down the chain untouched. ### 2. attemptAuthentication The filter calls `attemptAuthentication(request, response)`: - Reads the `username` and `password` **request parameters** (names configurable). - Constructs an **unauthenticated** `UsernamePasswordAuthenticationToken(username, password)` — `isAuthenticated() == false`, principal = username string, credentials = raw password. - Optionally copies request details (`WebAuthenticationDetailsSource` → remote IP, session id). - Calls `authenticationManager.authenticate(token)`. ### 3. AuthenticationManager / provider The default `AuthenticationManager` is a **`ProviderManager`** iterating `AuthenticationProvider`s. For username/password the relevant one is **`DaoAuthenticationProvider`**: - Loads the user with `UserDetailsService.loadUserByUsername(username)` (throws `UsernameNotFoundException` if missing). - Verifies the submitted password against the stored hash using the configured **`PasswordEncoder`** (e.g. `BCryptPasswordEncoder`). - Runs account checks (`UserDetailsChecker`): disabled, locked, expired, credentials-expired → corresponding `AuthenticationException`. - On success returns a **new, authenticated** `UsernamePasswordAuthenticationToken` carrying the `UserDetails` principal and its `GrantedAuthority` list, with `isAuthenticated() == true`. ### 4a. successfulAuthentication Back in the base filter: - **Session-fixation protection** runs (default `changeSessionId`) — a new session id is issued to defeat fixation attacks while keeping session attributes. - A fresh `SecurityContext` is created and the authenticated token set on it (`SecurityContextHolder`). - The context is **persisted** via the **`SecurityContextRepository`** — default **`HttpSessionSecurityContextRepository`**, storing it under `SPRING_SECURITY_CONTEXT` in the `HttpSession`. This is what makes form login **stateful**. - An `InteractiveAuthenticationSuccessEvent` is published. - The **`AuthenticationSuccessHandler`** runs (default: redirect to the saved request). - The chain does **not** continue — the response is committed by the handler. ### 4b. unsuccessfulAuthentication If the manager threw an `AuthenticationException`: - `SecurityContextHolder` is cleared. - The **`AuthenticationFailureHandler`** runs (default: redirect to `/login?error`). ### 5. Subsequent requests On later requests the credentials are **not** resent. Early in the chain, **`SecurityContextHolderFilter`** (Spring Security 6; formerly `SecurityContextPersistenceFilter`) asks the `SecurityContextRepository` to **load** the stored context from the session and place it in the `SecurityContextHolder` for the duration of the request, then clears it afterward. Authorization then sees an authenticated user without re-running the login filter. ## Key objects to name - `UsernamePasswordAuthenticationToken` — the credentials carrier, both before (unauthenticated) and after (authenticated). - `AuthenticationManager` → `ProviderManager` → `DaoAuthenticationProvider`. - `UserDetailsService`, `PasswordEncoder`. - `SecurityContext`, `SecurityContextHolder`, `SecurityContextRepository`. - `SecurityContextHolderFilter` (loads context per request). ## Gotchas - The **same class** `UsernamePasswordAuthenticationToken` represents both the unauthenticated request and the authenticated result — differentiate by `isAuthenticated()` and whether authorities are present. - Since Spring Security 6, the context is **not** auto-saved from the holder; the processing filter explicitly saves via the repository. Manually setting `SecurityContextHolder` without saving to the repository won't persist across requests. - Session-fixation protection changing the session id can surprise code that cached the old id.

  • Since Spring Security 6, why isn't setting SecurityContextHolder enough to stay logged in across requests?
    The holder is a per-request ThreadLocal, cleared at request end. Persistence requires saving the context through the SecurityContextRepository (e.g. HttpSessionSecurityContextRepository into the session). In Spring Security 6 this save is explicit — the framework no longer auto-copies the holder to the session, so you must call saveContext (the form-login filter does this for you).

saying these in an interview costs you the question

  • Saying the filter itself checks the password rather than the AuthenticationProvider.
  • Assuming SecurityContextHolder alone persists across requests in Spring Security 6.
  • Not recognizing that the same token class represents both unauthenticated and authenticated states.

context