Spring Security is Really Not that Hard. Internal working of Spring Security
This blog simplifies the inner workings of Spring Security, explaining key concepts like authentication, authorization, and filters. It covers the default behavior when adding spring-boot-starter-security, the role of DaoAuthenticationProvider, and the authentication flow, including how user credentials are validated and secured using JWT.
Santosh Mane
January 06, 2025
12 min read
When it comes to securing applications, Spring Security is often considered a complex and intimidating framework. However, once you understand how it works under the hood, you'll realize it's not as hard as it seems. This blog will break down the internal workings of Spring Security in a simple and approachable way. We will understand what happens internally when you enter a username and password and how it validates that it is really you.
The Core Concepts of Spring Security#
Before diving into its internal workings, let’s clarify some core concepts:
- Authentication: Verifying the identity of a user (e.g., checking username and password).
- Authorization: Determining what resources or actions a user is allowed to access.
- Filters: Intercepting requests and applying security logic before they reach the application.
- Security Context: Holding security-related information for the current user, such as their identity and roles.
Let’s understand the working of spring security#
Once you add the spring-boot-starter-security dependency in the pom.xml file Spring boot auto-configured the security with sensible defaults defined in WebSecurityConfiguration class.
In a Spring Boot application, SecurityFilterAutoConfiguration automatically registers the DelegationFilterProxy filter with the name springSecurityFilterChain.
From the above image you can understand that when client or user makes a request the request has to go through lot of filters as shown in above image before it reaches the Dispatcher servlet and from there to controllers.
So Once the request reaches to DelegatingFilterProxy, Spring delegates the processing to FilterChainProxy bean that utilizes the SecurityFilterChainProxy bean that utilizes the SecurityFilterChain to execute the list of all filters to be invoked for the current request.
So basically when you add the spring-boot-starter-security dependency it do’s the following things:
- Creates a bean named springSecurityFilterChain. Registers the filter with a bean named springSecurityFilterChain with the Servlet container for every request.
- HTTP basic authentication for authenticating requests made with remoting protocols and web services.
- Generate a default login form.
- Creates a user with a username of user and a password that is logged to the console.
- Protect the password storage with BCrypt.
- Enables logout feature.
- Enables other features such as protection from CSRF attacks and session fixation.
So this is all that you get by default when you add spring-boot-starter-security dependency.
Now Let’s Understand the internal working of spring security#
When user login at that time In order to authenticate the user against the user present in database or not with correct credentials, we need to make use of DaoAuthenticationProvider which is one of the AuthenticationProvider.
And for that we need to create the bean of AuthenticationManager in Confiuration class also we have to create bean for passwordEncoder with this we will be able to encode the user password during authentication process and will be used by DaoAuthenticationProvider.
Once we do this, we need to make our User
entity class implement the UserDetails
interface and override methods like getPassword()
and getUsername()
. This is necessary because Spring Security requires an implementation of UserDetails
to understand which fields in the User
entity should be used for authentication (e.g., username
and password
). Additionally, the UserDetails
interface helps specify which field in the entity will hold the roles or authorities of the user, enabling Spring Security to manage authorization effectively.
Next we have to create a UserService class which is going to be implemented by UserDetailsService and we have to override a method loadUserByUsername(String username).
Implementing UserDetailsService
and overriding loadUserByUsername(String username)
is the standard way to integrate Spring Security with your user database. This method is invoked during authentication to fetch the user details by the provided username (or email in our case).
During Login we will be providing the new object of UsernamePasswordAuthenticationToken, here the implementation class of AuthenticationManager is ProviderManager which will iterates through its configured AuthenticationProviders
. It finds a Provider
that supports the type of Authentication
object it received (in this case, DaoAuthenticationProvider
usually supports UsernamePasswordAuthenticationToken
).
Now Let’s understand how DaoAuthenticationProvider
works and helps us to Authenticate the User#
After the ProviderManager finds that DaoAuthenticationProvider supports the provided authentication object it will call the authenticate method of DaoAuthenticationProvider
.
Now DaoAuthenticationProvider
calls the UserDetailsService
's loadUserByUsername()
method, passing the username from the Authentication
object. The UserDetailsService
queries the database and returns a UserDetails
object containing the user's information (username, encoded password, authorities).
The DaoAuthenticationProvider
uses the configured PasswordEncoder
to compare the password provided by the user in the Authentication
object with the encoded password retrieved from the UserDetails
. If the passwords match and the user is valid , create a fully authenticated Authentication
object and If authentication fails (wrong password, user not found, etc.), throw an AuthenticationException
.
Understanding the Complete Authentication flow with diagram#
Following image shows the complete authentication flow when user login.
When user or client makes login request at that time we will call the AutheticationManager authenticate method by passing the Authentication object UsernamePasswordAuthenticationToken with principal as email and credentials as password.
Now the ProviderManager which is the ****implementation of AuthenticationManager will loop throug the list of AuthenticationProviders, this AuthenticationProvider Interface has two methods support and authenticate, all the implementation classes of AuthenticationProvider overrides this two methods.
One of the AbstractClass called AbstractUserDetailsAuthenticationProvider
implements the AuthenticationProvider interface and this abstract class is extended by DaoAuthenticationProvider
This DaoAuthenticationProvider's
authenticate method is invoked and it calls UserDetailService’s (which delegates controll to UserService) loadUserByUsername method by passing the username from the Authentication
object. The UserDetailsService
queries the database and returns a UserDetails
object containing the user's information (username, encoded password, authorities).
The DaoAuthenticationProvider
uses the configured PasswordEncoder
to compare the password provided by the user in the Authentication
object with the encoded password retrieved from the UserDetails
. If the passwords match and the user is valid, create a fully authenticated Authentication
object and If authentication fails (wrong password, user not found, etc.), throw an AuthenticationException
.
Using JWT Token for Authentication#
Once the authentication is done by DaoAuthenticationProvider we create the jwt access token with the help of jwt service. During jwt token creation we will set the userId as the subject and add the remaining details like email and roles in claims. The roles that we added will be used during authorizing the user.
Note:- You have to add the following dependencies in the pom.xml file to use jwt token in spring boot application.
Requests made for signup and login will be permitted and not be authenticated, but all othe requests that user will make after login by sending the accessToken in the header will go through securityFilterChain for authentication.
Authenticating the user with the provided accessToken in the header.#
So far we have seen that when user makes the login request after signup user will get an accessToken and that accessToken will be passed in the next requests made by user to access any content or resource from database.
Let’s breakdown the code for JwtAuthFilter to understand ut
The JwtAuthFilter
class is a custom Spring Security filter used to validate JWT (JSON Web Token) authentication in an application. Here’s what the filter does, step-by-step:
- Extract the Token:
- It checks the
Authorization
header of the incoming HTTP request for a token. If the header is missing or does not start with"Bearer"
, it skips further processing and lets the request proceed to further filter that is UsernamePasswordAuthenticationFilter which will throw Authentication exception as it will not get the authentication object.
- It checks the
- Validate and Parse the Token:
- If a token is found, it extracts the token value and uses
JwtService
to decode and extract the user ID embedded in the token.
- If a token is found, it extracts the token value and uses
- Set Authentication:
- If the user ID is valid and the
SecurityContextHolder
does not already have an authentication object:- The filter fetches the corresponding user details using
UserService
. - It creates a
UsernamePasswordAuthenticationToken
with the user’s details and authorities. - The token is stored in the
SecurityContextHolder
, marking the request as authenticated for the next filter i.e UsernamePasswordAuthenticationFilter will find that authentication object is present in security context so no need to perform authentication and it will simply pass the request to next filter in chain and from here the request is now send to dispatcher servlet and from dispatcher servlet to controller.
- The filter fetches the corresponding user details using
- If the user ID is valid and the
- Handle Exceptions:
- Any exceptions during the process are resolved using the
HandlerExceptionResolver
, ensuring proper error handling and response generation.
- Any exceptions during the process are resolved using the
- Continue the Filter Chain:
- After processing, the filter calls
filterChain.doFilter()
to pass the request to the next filter in the chain and the request is passed to UsernamePasswordAuthenticationFilter.
- After processing, the filter calls
The above image shows the complete flow that we have discussed so far.
Conclusion#
So, as we've seen, Spring Security can seem a bit intimidating at first glance. But once you get a handle on how it works behind the scenes the filters, the AuthenticationManager, DaoAuthenticationProvider, and the UserDetailsService it all starts to make sense. You can actually configure and tweak your app's security pretty confidently. It's a powerful tool, and honestly, it's not as tough as it looks.