How does ProviderManager work? Walk through how it iterates over its AuthenticationProviders and uses the parent manager.
answer
- chain-of-responsibility over AuthenticationProviders
- supports() filter, first non-null wins
- AccountStatus/InternalService = fatal, break
- other AuthenticationException = remember, continue
- parent manager fallback, then ProviderNotFoundException
basics
~20 sProviderManager is the standard AuthenticationManager. It holds a list of AuthenticationProviders and asks each whether it supports the token; the first one that supports it and returns a non-null result wins. If none succeed, it delegates to an optional parent manager.
solid answer
~40 sProviderManager is the default AuthenticationManager implementation. It iterates its ordered List<AuthenticationProvider>, calling provider.supports(authentication.getClass()) and skipping providers that return false. For a supporting provider it calls authenticate(); the first non-null result is the winner and iteration stops. If a provider throws an AccountStatusException or InternalAuthenticationServiceException, iteration stops immediately (these are fatal). Other AuthenticationExceptions are remembered as 'lastException' and iteration continues, so a later provider can still succeed. If no configured provider produces a result, ProviderManager delegates to its optional parent AuthenticationManager (used in hierarchical setups, e.g. a global parent shared by multiple filter chains). If neither the list nor the parent authenticates, it rethrows the last exception, or throws ProviderNotFoundException if nothing supported the token. On success it erases credentials and publishes an authentication event.
code
java · 23 lines@Configuration
class SecurityConfig {
// Compose a ProviderManager from multiple providers
@Bean
AuthenticationManager authenticationManager(
DaoAuthenticationProvider daoProvider,
ApiKeyAuthenticationProvider apiKeyProvider) {
ProviderManager manager =
new ProviderManager(List.of(apiKeyProvider, daoProvider));
// Optionally keep raw credentials on the returned token:
// manager.setEraseCredentialsAfterAuthentication(false);
return manager;
}
@Bean
DaoAuthenticationProvider daoProvider(UserDetailsService uds, PasswordEncoder encoder) {
DaoAuthenticationProvider p = new DaoAuthenticationProvider();
p.setUserDetailsService(uds);
p.setPasswordEncoder(encoder);
return p;
}
}go deeper
Know that ProviderManager delegates to a list of providers and the first success wins.
Explain supports() filtering, first-non-null-wins, and the parent fallback plus ProviderNotFoundException.
Detail the fatal-vs-continuable exception handling and credential erasure/event publishing side effects.
Design hierarchical manager topologies across multiple filter chains and reason about ordering, short-circuit semantics, and audit event flow.
## ProviderManager: the standard implementation `ProviderManager` is the concrete `AuthenticationManager` used in almost every Spring Security application. Instead of authenticating directly, it **delegates to a chain of `AuthenticationProvider` beans**. This is the *chain-of-responsibility* pattern applied to authentication. ## The AuthenticationProvider SPI Each delegate implements: ```java public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); } ``` `supports(Class)` lets a provider declare which token types it handles (e.g. `DaoAuthenticationProvider` supports `UsernamePasswordAuthenticationToken`). ## Iteration algorithm (the important part) Given an incoming `Authentication`, `ProviderManager.authenticate()` loops over its `List<AuthenticationProvider>`: 1. For each provider, call `provider.supports(toTest.getClass())`. If `false`, **skip** it. 2. If it supports the type, call `provider.authenticate(authentication)`. 3. **If the provider returns a non-null result**, remember it and **break** out of the loop — the first success wins. 4. **If the provider throws `AccountStatusException` or `InternalAuthenticationServiceException`**, the loop is aborted immediately and that exception is (re)thrown — these are considered *fatal* and should not be masked by trying other providers. `AccountStatusException` covers `DisabledException`, `LockedException`, `AccountExpiredException`, `CredentialsExpiredException`. 5. **If the provider throws any other `AuthenticationException`** (e.g. `BadCredentialsException`), it is stored in `lastException` and iteration **continues** to the next provider. This lets a different provider authenticate the same credentials a different way. ## The parent manager `ProviderManager` can hold an optional **parent `AuthenticationManager`**. If, after exhausting its own provider list, no result was produced (and no fatal exception thrown), it calls `parent.authenticate(authentication)`. This supports **hierarchical configurations**: multiple `SecurityFilterChain`s each get their own local `ProviderManager` but share a common global parent (the `AuthenticationManager` built from `AuthenticationConfiguration`). A parent success/exception is also captured. ## Final resolution - If a result was obtained (from a provider or the parent) → success path. - Else if a `lastException`/`lastParentException` exists → it is rethrown. - Else (nothing supported the token) → throw **`ProviderNotFoundException`** (a subtype of `AuthenticationException`). ## Post-success behavior - **Credential erasure**: by default `eraseCredentialsAfterAuthentication = true`, so if the result implements `CredentialsContainer`, `ProviderManager` calls `eraseCredentials()` to null out the raw password in memory. Note: it only erases the *returned* object, and only if the result is not the parent's. - **Event publishing**: via an `AuthenticationEventPublisher` it publishes an `AuthenticationSuccessEvent` (or failure events), which drives audit/lockout listeners. ## Gotchas - **Order matters**: providers are tried in list order; the first non-null wins. - **Fatal-vs-continuable exceptions**: an account-status problem short-circuits the chain so users get a clear 'account locked' rather than a misleading 'bad credentials' from a later provider. - **Credential erasure surprises**: if you cache the returned Authentication and expect the password to still be present, erasure will have nulled it. You can disable via `setEraseCredentialsAfterAuthentication(false)`. - **ProviderNotFoundException** specifically means *no provider supported the token type* — often a misconfiguration (wrong token class, no matching provider registered).
- If provider A throws BadCredentialsException but provider B (later in the list) can authenticate the same token, what happens?Iteration continues after A's non-fatal exception; B authenticates successfully and its result wins. A's exception is discarded because a result was produced.
- Why are AccountStatusException and InternalAuthenticationServiceException treated as fatal?They represent definitive account/system problems (locked/disabled account, or an infrastructure failure like a DB down). Continuing could mask the real cause and produce a misleading BadCredentials outcome, so ProviderManager aborts immediately.
saying these in an interview costs you the question
- Saying every provider is called and results are merged
- Claiming a BadCredentialsException from the first provider stops the whole chain
- Not knowing about the parent manager
- Thinking ProviderManager itself checks passwords (the provider does)