Spring Security authentication follows a well-defined, layered workflow.
Each component has one clear responsibility, and authentication is achieved by collaboration between these components — not by any single class.
Understanding this flow is mandatory for:
- real-world backend development
- debugging authentication issues
- JWT / custom authentication implementations
- Spring Security interviews
HTTP Request
↓
Authentication Filter
↓
AuthenticationManager
↓
AuthenticationProvider
↓
UserDetailsService
↓
PasswordEncoder
↓
Authentication Result
↓
SecurityContext
Important rule
Filters do not authenticate users. AuthenticationProviders do.
We will walk through a username + password (JDBC) authentication flow, which is the foundation for most systems.
- Intercepts incoming HTTP request
- Extracts credentials
- Creates an Authentication token
- Delegates authentication
UsernamePasswordAuthenticationFilter- Reads username and password from request
- Creates an unauthenticated Authentication object
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);- Passes token to
AuthenticationManager
authenticationManager.authenticate(authRequest);The filter never validates credentials.
Spring Security represents credentials using Authentication.
authenticated = false
principal = username
credentials = raw passwordauthenticated = true
principal = UserDetails
credentials = null
authorities = rolesThe same object type is reused, but its state changes.
- Central entry point for authentication
- Delegates authentication to providers
Authentication authenticate(Authentication authentication);ProviderManager- Holds a list of AuthenticationProviders
- Iterates through them
- Finds a provider that
supports()the token type
This is where actual authentication happens.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication);
boolean supports(Class<?> authentication);
}Used for:
- database-backed authentication
- JPA / JDBC / UserDetailsService
- Receives
UsernamePasswordAuthenticationToken - Calls
supports()to verify compatibility - Delegates user lookup to
UserDetailsService - Delegates password comparison to
PasswordEncoder - Returns authenticated token
Responsible for loading user data, not authentication.
UserDetails loadUserByUsername(String username);@Service
public class CustomUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(), // hashed password
user.getAuthorities()
);
}
}- Does not compare passwords
- Only fetches user details
Handles secure password comparison.
boolean matches(CharSequence raw, String encoded);BCryptPasswordEncoder- Raw password from request
- Hashed password from database
- Encoder hashes raw password
- Compares hashes securely
passwordEncoder.matches(rawPassword, storedHash);Passwords are never decrypted.
If credentials are valid:
- AuthenticationProvider returns an authenticated Authentication object
- Authorities are attached
- Credentials are erased
- Authentication is stored in
SecurityContext
SecurityContextHolder.getContext()
.setAuthentication(authentication);Stores authentication details for the current request/thread.
SecurityContextHolder.getContext().getAuthentication();Used by:
- authorization checks
@PreAuthorize- controllers and services
If authentication fails:
-
Provider throws
AuthenticationException -
Filter catches exception
-
Security returns:
401 Unauthorized(REST)- login error (web apps)
Common exceptions:
BadCredentialsExceptionUsernameNotFoundExceptionDisabledException
Client Request
↓
Authentication Filter
↓
AuthenticationManager (ProviderManager)
↓
AuthenticationProvider
↓
UserDetailsService ──► Database
↓
PasswordEncoder
↓
Authenticated Authentication
↓
SecurityContextHolder
↓
Controller / Service
| Component | Username/Password | JWT |
|---|---|---|
| Filter | Login filter | JWT filter |
| AuthenticationManager | Yes | Yes |
| AuthenticationProvider | DAO provider | JWT provider |
| UserDetailsService | Yes | Optional |
| PasswordEncoder | Yes | No |
| Session | Stateful | Stateless |
Same architecture, different credentials.
- Filters authenticate users ❌
- UserDetailsService validates passwords ❌
- Passwords are decrypted ❌
- AuthenticationManager contains logic ❌
Correct understanding:
- Filters extract data
- Providers authenticate
- Encoders compare hashes
| Component | Responsibility |
|---|---|
| Filter | Credential extraction |
| AuthenticationManager | Delegation |
| AuthenticationProvider | Authentication logic |
| UserDetailsService | User lookup |
| PasswordEncoder | Secure comparison |
| SecurityContext | Store result |
AuthenticationProvider.
To load user data from database.
To securely compare hashed passwords.
Yes, ProviderManager supports multiple providers.
Inside a custom AuthenticationProvider.
-
Authentication is a pipeline, not a single class
-
Filters extract credentials
-
AuthenticationManager delegates
-
AuthenticationProvider performs validation
-
UserDetailsService loads user data
-
PasswordEncoder handles hashing
-
SecurityContext stores authentication
-
This workflow underpins all Spring Security authentication types