
AuthenticationManager, Providers, and UserDetailsService
An examination of Spring Security's core components: AuthenticationManager, AuthenticationProvider, and UserDetailsService, and how they interact.
In Spring Security, the AuthenticationManager orchestrates authentication, a process often misunderstood as a single step where credentials are checked against a database. In reality, it is a layered delegation chain. The system decouples the acceptance of credentials from the validation logic and the storage of user data. This separation allows you to swap password encoders, change database backends, or add multi-factor authentication without rewriting the core security filter chain.
This is Part 6 of the Spring Security Filter Chain Mastery series. To understand how Spring Security authenticates a user, we must examine the three core interfaces that form this chain: AuthenticationManager, AuthenticationProvider, and UserDetailsService.
The Orchestrator: AuthenticationManager
At the top of the hierarchy sits the AuthenticationManager. Its role is purely managerial. It does not know how to validate a password, check an API key, or verify a certificate. It only knows how to delegate.
The interface defines a single method:
Authentication authenticate(Authentication authentication)
throws AuthenticationException;When an Authentication object (such as a UsernamePasswordAuthenticationToken) enters the system, the AuthenticationManager iterates through a list of registered AuthenticationProvider instances. It asks each provider if it supports the type of authentication presented. The first provider that says "yes" and successfully validates the credentials returns a fully authenticated Authentication object. If no provider succeeds, an AuthenticationException is thrown.
In most modern Spring Boot applications, you do not implement AuthenticationManager directly. Instead, you rely on the AuthenticationConfiguration class, which automatically wires a ProviderManager (the default implementation) with the providers you define in your security configuration.
The Validators: AuthenticationProvider
The AuthenticationProvider interface is where the actual validation logic resides. It defines two methods:
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);The supports method is critical. It acts as a gatekeeper. When the AuthenticationManager receives a token, it checks every provider's supports method. If supports returns false, that provider is skipped entirely.
This design enables modular authentication strategies. You might have:
DaoAuthenticationProvider: Validates username/password against a database.JwtAuthenticationProvider: Validates a JSON Web Token signature.OAuth2AuthorizationCodeAuthenticationProvider: Handles the OAuth2 flow.
Each provider is responsible for its own validation mechanics. For example, DaoAuthenticationProvider handles the comparison of passwords, while JwtAuthenticationProvider handles cryptographic signature verification. They do not share state; they are stateless validators.
The Data Source: UserDetailsService
The most common point of confusion is the role of UserDetailsService. Developers often expect this interface to validate credentials. It does not.
UserDetailsService has a single responsibility: load user data.
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;It returns a UserDetails object, which contains:
- The username.
- The encoded password.
- The authorities (roles/permissions).
- Account status flags (enabled, locked, expired).
Crucially, UserDetailsService knows nothing about the incoming credentials. It simply fetches the stored record for the given identifier. This isolation is intentional. It means you can change your user storage from a relational database to LDAP, or from SQL to NoSQL, by simply replacing the UserDetailsService implementation. The validation logic in the AuthenticationProvider remains unchanged because it only interacts with the UserDetails contract, not the underlying storage mechanism.
The Workflow: A Concrete Scenario
Let’s trace a standard username/password login to see how these components interact.
- Input: A user submits a login form with
username="alice"andpassword="secret123". - Filter:
UsernamePasswordAuthenticationFiltercaptures this data and creates an unauthenticatedUsernamePasswordAuthenticationToken. - Manager Delegation: The filter calls
authenticationManager.authenticate(token). - Provider Selection: The
ProviderManageriterates through its list. It findsDaoAuthenticationProvider. The provider'ssupportsmethod returnstruebecause the token is aUsernamePasswordAuthenticationToken. - Data Loading:
DaoAuthenticationProvidercallsuserDetailsService.loadUserByUsername("alice"). - Data Retrieval: Your custom
UserDetailsServiceimplementation queries the database and returns aUserDetailsobject containing the encoded password (e.g.,$2a$10$...). - Validation:
DaoAuthenticationProviderretrieves the raw password from the token ("secret123") and the encoded password fromUserDetails. It passes both to aPasswordEncoder(e.g.,BCryptPasswordEncoder). - Result: If the encoder confirms the match,
DaoAuthenticationProviderreturns a newAuthenticationobject withisAuthenticated() = trueand the loaded authorities. - Security Context: The
SecurityContextHolderstores this authenticated token, granting Alice access to protected resources.
Configuration Mechanics
In Spring Boot 2.x and 3.x, the wiring is handled by the AuthenticationConfiguration. When you extend WebSecurityConfigurerAdapter (deprecated in newer versions) or use the new SecurityFilterChain bean, you are configuring the AuthenticationManager.
For example, to customize how users are loaded, you define a UserDetailsService bean:
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}Spring Security automatically injects this bean into the DaoAuthenticationProvider, which is part of the default AuthenticationManager. You do not need to manually wire the manager to the service. The framework assumes that any UserDetailsService bean should be used by the default authentication providers.
If you need multiple authentication sources (e.g., one for internal users, one for partners), you define multiple AuthenticationProvider beans. The ProviderManager will attempt them in order until one succeeds.
Conclusion
The power of Spring Security’s design lies in its separation of concerns. AuthenticationManager orchestrates the flow. AuthenticationProvider validates specific credential types. UserDetailsService isolates data retrieval. By understanding this chain, you can troubleshoot authentication failures more effectively. Is the error an BadCredentialsException? The provider failed validation. Is it a UsernameNotFoundException? The UserDetailsService couldn’t find the record. Each component has a distinct role, and understanding their interaction is key to building secure, flexible authentication systems.
Related posts
Building a Custom UserDetailsService with Spring Security
Learn how to implement a custom UserDetailsService in Spring Security to handle user loading and GrantedAuthority logic.
SecurityFilterChain in Spring Security 6
A technical walkthrough of configuring SecurityFilterChain in Spring Security 6 using the Lambda DSL and RequestMatchers for Java developers.
Building a Custom Authentication Provider in Spring Security
This article covers the implementation of a custom authentication mechanism within Spring Security using a dedicated AuthenticationProvider.