Careeroza
The Authentication Flow
From Credentials to Authenticated User
1. BasicAuthenticationFilter reads "Authorization: Basic YXNoYTphc2hhMTIz"
|
v
2. creates UsernamePasswordAuthenticationToken("asha", "asha123") authenticated = false
|
v
3. AuthenticationManager (ProviderManager)
| asks each AuthenticationProvider: supports(this token type)?
v
4. DaoAuthenticationProvider
|-- UserDetailsService.loadUserByUsername("asha") -> UserDetails (hash, roles)
|-- PasswordEncoder.matches("asha123", "$2a$10$...") -> true
v
5. returns UsernamePasswordAuthenticationToken(userDetails, null, [ROLE_CUSTOMER]) authenticated = true
|
v
6. filter stores it: SecurityContextHolder.getContext().setAuthentication(auth)
|
v
7. AuthorizationFilter checks the rules -> controller runs
Wrong password -> BadCredentialsException -> 401Manager and Providers
AuthenticationManager is a one-method interface: Authentication authenticate(Authentication). Its usual implementation, ProviderManager, holds a list of AuthenticationProviders and tries each one that supports the token type. This is how one app can accept passwords, JWTs and LDAP at the same time.
Example
A toy ProviderManager with two providers:
import java.util.*;
public class Main {
sealed interface Token permits Password, ApiKey {}
record Password(String username, String password) implements Token {}
record ApiKey(String key) implements Token {}
record AuthResult(String principal, List<String> authorities) {}
interface AuthenticationProvider {
boolean supports(Token t);
AuthResult authenticate(Token t); // throws on bad credentials
}
static class DaoProvider implements AuthenticationProvider {
public boolean supports(Token t) { return t instanceof Password; }
public AuthResult authenticate(Token t) {
Password p = (Password) t;
if (p.username().equals("asha") && p.password().equals("asha123"))
return new AuthResult("asha", List.of("ROLE_CUSTOMER"));
throw new IllegalStateException("BadCredentialsException: Bad credentials");
}
}
static class ApiKeyProvider implements AuthenticationProvider {
public boolean supports(Token t) { return t instanceof ApiKey; }
public AuthResult authenticate(Token t) {
if (((ApiKey) t).key().equals("partner-delhivery-key")) return new AuthResult("delhivery", List.of("ROLE_PARTNER"));
throw new IllegalStateException("BadCredentialsException: Invalid API key");
}
}
static AuthResult providerManager(List<AuthenticationProvider> providers, Token token) {
for (AuthenticationProvider p : providers)
if (p.supports(token)) return p.authenticate(token);
throw new IllegalStateException("ProviderNotFoundException");
}
public static void main(String[] args) {
List<AuthenticationProvider> providers = List.of(new DaoProvider(), new ApiKeyProvider());
Token[] attempts = { new Password("asha", "asha123"), new ApiKey("partner-delhivery-key"), new Password("asha", "nope") };
for (Token t : attempts) {
try {
System.out.println(t + " -> " + providerManager(providers, t));
} catch (IllegalStateException e) {
System.out.println(t + " -> 401 " + e.getMessage());
}
}
}
}Output
Password[username=asha, password=asha123] -> AuthResult[principal=asha, authorities=[ROLE_CUSTOMER]] ApiKey[key=partner-delhivery-key] -> AuthResult[principal=delhivery, authorities=[ROLE_PARTNER]] Password[username=asha, password=nope] -> 401 BadCredentialsException: Bad credentials
Spring Configures This for You
When you declare a UserDetailsService bean and a PasswordEncoder bean, Spring Security creates a DaoAuthenticationProvider and a ProviderManager automatically. You only build them yourself in special cases, such as a custom login endpoint:
@Bean
AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); // Spring Security 6.3+
provider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(provider);
}Note: A custom AuthenticationProvider (OTP login, partner API keys) is covered in the advance level.