Spring security authentication
Spring security authentication
بِسْمِ ٱللّٰهِ ٱلرَّحْمٰنِ ٱلرَّحِيمِ
In the previous article, we saw how Spring Security builds its filter chain and how every request marches through it. But we left one question unanswered — when a request carries a username and password, who actually checks it? Who decides if this person is who they claim to be? That's exactly what we're digging into today. We'll follow a login request from the moment it hits the filter all the way to the moment the user's identity gets stamped and stored — and we'll also see what happens when it goes wrong.
1) Security context:
Before we trace the flow, we need to understand where Spring Security stores the result of authentication and for that let me introduce the heart of spring security.
SecurityContextHolder:
This is where spring stores the currently authenticated user. Think of it as a global shelf that Spring Security maintains for the duration of a request. It uses ThreadLocal under the hood, meaning every thread automatically carries its own isolated copy — no sharing, no collisions between concurrent requests.
SecurityContext:
Sitting inside the holder is the SecurityContext. It has one job: hold the Authentication object.
Authentication:
The Authentication interface serves two roles depending on where you are in the flow:
Before authentication: it's an input carrying the user's credentials (username + password)
After authentication: it's a verified record of who the current authenticated user is.
And it contains 3 main parts:
principal: Identifies the user. When authenticating with a username/password this is often an instance ofUserDetails.credentials: Often a password. In many cases, this is cleared after the user is authenticated, to ensure that it is not leaked.authorities: TheGrantedAuthorityinstances are high-level permissions the user is granted. Two examples are roles and scopes.
And here's how you read the current user anywhere in your app:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Now that we have defined these parts we can start the actual flow.
2) The flow:
So when the user make a post request to the login endpoint containing its username and password it will pass through the SecurityFilterChain as we explained in the previous article (that I highly recommend to read it before continuing) until it hits the filter that cares about login.
UsernamePasswordAuthentictionFilter:
Extracts the username and password from the request
Wraps them into a
UsernamePasswordAuthenticationToken— at this point,authenticated = false.Passes the token to the
AuthenticationManager
You may ask why doesn't the filter just verify the credentials itself? Because filters shouldn't know anything about how your app stores users. That concern belongs somewhere deeper. The filter's only job is to extract and forward.
AuthenticationManager:
AuthenticationManager is just an interface with one method: authenticate(). It receives the unverified token and is responsible for returning a fully authenticated one — or throwing an exception if something goes wrong.
While the implementation of AuthenticationManager could be anything, the most common implementation is ProviderManager, and that's where things get interesting.
ProviderManager:
ProviderManager doesn't authenticate anything directly either. Instead, it holds a list of AuthenticationProvider instances and loops through them asking: "Can you handle this token type?" The first one that says yes gets to do the work.
This design is the key insight: it means your app can support multiple authentication mechanisms simultaneously — username/password, OAuth, LDAP, API keys — each handled by its own provider, all managed by one ProviderManager.
AuthenticationProvider:
As we have talked earlier there are many authentication providers OAuthAuthenticationProvider, LdapAuthenticationProvider, and others. We'll focus on DaoAuthenticationProvider, which handles the classic username/password case.
It does two things:
Calls
UserDetailsService.loadUserByUsername()to fetch the user record from your databaseUses
PasswordEncoderto compare the submitted password against the stored hash
If credentials match: it returns a fully populated Authentication object with authenticated = true, complete with the user's authorities. ProviderManager hands this back up the chain, and the filter stores it in the SecurityContextHolder. The user is in.
If credentials don't match: DaoAuthenticationProvider throws a BadCredentialsException. ProviderManager catches it, and the filter's failure handler takes over — typically redirecting to /login?error or returning a 401. The SecurityContextHolder stays empty. The user is out.
What's Next
We've now covered the complete username/password authentication flow. But after the user authenticates we need to know what he can access and what not. For that we will explain how authorization works in spring security.
Sources:
Spring security documentation: https://docs.spring.io/spring-security/reference/servlet/authentication/index.html