skip to content

Explain the parent AuthenticationManager mechanism in ProviderManager and the credential-erasure behavior. Why do they exist?

level: seniorimportance: should knowfreq 40%

answer

  1. parent = shared global manager, local list falls back to it
  2. per-SecurityFilterChain manager + AuthenticationConfiguration parent
  3. eraseCredentialsAfterAuthentication = true default
  4. CredentialsContainer.eraseCredentials nulls the password
  5. child doesn't re-erase parent's result

basics

~20 s

A ProviderManager can have a parent AuthenticationManager it falls back to when none of its own providers authenticate. This lets multiple filter chains share a common global manager. After a successful authentication, ProviderManager erases the raw credentials from the returned token by default to reduce in-memory exposure.

solid answer

~40 s

ProviderManager supports a hierarchy: each instance holds an ordered list of local AuthenticationProviders plus an optional parent AuthenticationManager. If no local provider produces a result (and none throws a fatal exception), it delegates to the parent. This is how Spring Security shares a single 'global' AuthenticationManager (built from AuthenticationConfiguration) across many per-SecurityFilterChain managers — common providers live in the parent, chain-specific ones locally. Separately, eraseCredentialsAfterAuthentication defaults to true: after success, if the returned Authentication implements CredentialsContainer, ProviderManager calls eraseCredentials() to null the raw password/secret so it isn't retained in the SecurityContext (and thus not in the session). A subtlety: a ProviderManager will NOT erase credentials on results returned by its parent (the parent handles its own erasure), avoiding double handling. Disable erasure with setEraseCredentialsAfterAuthentication(false) if a downstream needs the raw credential.

code

java · 15 lines
java
@Configuration
class SecurityConfig {

    // A local ProviderManager with a chain-specific provider, falling back to a shared parent
    @Bean
    AuthenticationManager chainManager(
            ApiKeyAuthenticationProvider apiKeyProvider,
            AuthenticationConfiguration authConfig) throws Exception {
        AuthenticationManager global = authConfig.getAuthenticationManager(); // the parent
        ProviderManager local = new ProviderManager(List.of(apiKeyProvider), global);
        // Keep raw credentials on the returned token only if truly needed:
        // local.setEraseCredentialsAfterAuthentication(false);
        return local;
    }
}

go deeper

for a junior

Know a ProviderManager can fall back to a parent manager and that passwords get cleared after login.

for a middle

Explain that local providers run first, parent is the fallback, and erasure is on by default via CredentialsContainer.

for a senior

Articulate the per-filter-chain-plus-global-parent topology and the parent-not-re-erased subtlety.

for a principal

Design authentication topologies across chains, decide erasure policy against session-persistence risk, and avoid manager cycles/mis-scoped providers.

## The parent manager: composing a hierarchy `ProviderManager` has two collaborators: a `List<AuthenticationProvider>` (its own delegates) and an optional **parent `AuthenticationManager`**. `authenticate()` flow with a parent: 1. Try each local provider (supports → authenticate), as described in the standard algorithm. 2. If no local provider returned a result **and** no fatal exception was thrown, and a parent exists, call `parent.authenticate(authentication)`. 3. The parent's result or exception is then folded into the final resolution (success returns it; a parent `AuthenticationException` becomes the `lastException` candidate; `ProviderNotFoundException` from the parent is treated specially so it doesn't mask a more meaningful local exception). ### Why a parent exists Spring Security builds an `AuthenticationManager` **per `SecurityFilterChain`** through `HttpSecurity`/`AuthenticationManagerBuilder`. To avoid re-registering the same providers everywhere, there is typically a **global (parent) `AuthenticationManager`** exposed by `AuthenticationConfiguration`. Each local `ProviderManager` can add chain-specific providers while falling back to the shared parent for common ones (e.g. the DAO/username-password provider). This yields a clean *hierarchy* where shared authentication logic is defined once. It also isolates chains: a provider added to one chain doesn't automatically leak into another, but the common parent is reused. ### Gotcha: don't create accidental cycles Because managers can reference a parent, misconfiguration can create loops or a chain that never reaches the intended provider. When manually wiring `ProviderManager`s, be deliberate about which providers are local vs in the parent. ## Credential erasure A freshly authenticated `Authentication` (e.g. `UsernamePasswordAuthenticationToken`) initially still carries the **raw credential** (the password) in `getCredentials()`. Leaving it there means the password lingers in the `SecurityContext` — and if the context is persisted (HTTP session, `SecurityContextRepository`), potentially in session storage. `ProviderManager` mitigates this with **`eraseCredentialsAfterAuthentication` (default `true`)**: - After a successful `authenticate()`, if the result implements `CredentialsContainer`, `ProviderManager` calls `result.eraseCredentials()`, which nulls the sensitive fields (credentials, and often the password inside a `UserDetails` principal if that also implements `CredentialsContainer`). - Built-in tokens like `UsernamePasswordAuthenticationToken` and `User` (the default `UserDetails`) implement `CredentialsContainer`, so this works out of the box. ### Subtleties - **Parent results are not re-erased** by the child `ProviderManager`; the manager that actually produced the result performs erasure, avoiding redundant work and respecting the producer's policy. - **Custom tokens**: if your token doesn't implement `CredentialsContainer`, erasure does nothing — implement it (or extend a built-in) to benefit. - **When to disable**: if a later step genuinely needs the raw credential (e.g. re-authenticating downstream, or a test), call `setEraseCredentialsAfterAuthentication(false)` — but understand you're keeping secrets in memory/session longer. ## When to use / reason about - Use the **parent** when structuring multiple filter chains that should share common authentication logic. - Keep **erasure on** in production for defense-in-depth; only disable with a concrete, justified need. - Remember erasure runs **only on success** — a failed authentication never reaches the erasure step.

  • Why does a child ProviderManager avoid erasing credentials on a result returned by its parent?
    The manager that actually produced the result is responsible for its own erasure policy. Re-erasing would be redundant and could conflict with the parent's configuration, so the child leaves parent-produced results untouched.
  • What must a token implement for erasure to actually null its credentials?
    CredentialsContainer. Built-in tokens like UsernamePasswordAuthenticationToken and the default User principal implement it; a custom token must implement eraseCredentials() (or extend a built-in) for erasure to have any effect.

saying these in an interview costs you the question

  • Thinking the parent is tried before local providers
  • Believing erasure happens on failed authentication too
  • Assuming any custom token gets credential erasure automatically
  • Confusing 'parent' with a superclass rather than a delegated manager

context