- Published on
Discussing Spring security servlet authentication architecture (Main classes/interfaces)
- Authors
- Name
- Alae Touba
Introduction
If you go to the official Spring Framework website and start reading the documentation about the servlet authentication architecture, you’ll see that they start by discussing these main classes/interfaces:
SecurityContextHolder - The SecurityContextHolder is where Spring Security stores the details of who is authenticated.
SecurityContext - is obtained from the SecurityContextHolder and contains the Authentication of the currently authenticated user.
Authentication - Can be the input to AuthenticationManager to provide the credentials a user has provided to authenticate or the current user from the SecurityContext.
GrantedAuthority - An authority that is granted to the principal on the Authentication (i.e. roles, scopes, etc.)
AuthenticationManager - the API that defines how Spring Security’s Filters perform authentication.
ProviderManager - the most common implementation of AuthenticationManager.
AuthenticationProvider - used by ProviderManager to perform a specific type of authentication.
Request Credentials with AuthenticationEntryPoint - used for requesting credentials from a client (i.e. redirecting to a log in page, sending a WWW-Authenticate response, etc.)
AbstractAuthenticationProcessingFilter - a base Filter used for authentication. This also gives a good idea of the high level flow of authentication and how pieces work together.
\
The authentication flow
Before diving a bit deeper into each one of these classes/interfaces, let's take a look at the global picture of the flow of authentication in Spring Security, focusing on a simple username/password authentication scenario (like the one used with a web form login page or when working with an API using the Basic Auth mechanism). The steps below outline the process, identifying the key components, classes, and interfaces involved in each step:
1. User Submits Credentials:
The user submits their username and password through a login form or any other authentication mechanism.
2. Filter Chain Initiates:
The request passes through the servlet filter chain (requests interceptors) configured in your Spring Security application.
The UsernamePasswordAuthenticationFilter
, an implementation of AbstractAuthenticationProcessingFilter
, is a key player in intercepting and handling username/password authentication requests.
3. UsernamePasswordAuthenticationFilter Attempts Authentication:
The UsernamePasswordAuthenticationFilter
attempts to extract the username and password from the incoming request.
It creates an instance of UsernamePasswordAuthenticationToken
, implementing the Authentication
interface, to hold the authentication data.
4. Authentication Manager is Invoked:
The UsernamePasswordAuthenticationFilter
calls the configured AuthenticationManager
to perform authentication.
The ProviderManager
, a default implementation of AuthenticationManager
, manages a list of AuthenticationProvider
instances.
5. Authentication Providers are Consulted:
The ProviderManager
iterates through its list of AuthenticationProvider
instances.
Each AuthenticationProvider
is responsible for a specific type of authentication.
In the case of username/password authentication, the DaoAuthenticationProvider
is commonly used. It relies on a UserDetailsService
to retrieve user details from a data source.
6. UserDetailsService Retrieves User Details:
The DaoAuthenticationProvider
calls the configured UserDetailsService
to retrieve user details based on the provided username.
The UserDetailsService
typically loads user details from a database or any other persistent storage.
7. Password Comparison:
The retrieved user details include the stored password.
The DaoAuthenticationProvider
compares the stored password with the one provided by the user during login.
8. Authentication Success or Failure:
If the passwords match, the authentication is considered successful.
The AuthenticationProvider
returns a fully populated Authentication
object with the user's details and authorities.
If the authentication fails, an exception, usually BadCredentialsException
, is thrown.
9. SecurityContext is Updated:
Upon a successful authentication, the SecurityContextHolder
is updated with the new SecurityContext
.
The SecurityContext
now holds the authenticated Authentication
object.
10. User is Authenticated:
The user is now considered authenticated, and subsequent requests within the same session will carry the user's security context.
Discussing the main authentication components (classes/interfaces involved)
Now, let’s try to dive a bit deeper into each one of these (and some more).
AbstractAuthenticationProcessingFilter: Foundation for Authentication Filters
As a concept, a filter is a component that is used to intercept and process HTTP requests and responses in a web application. Filters in Spring Security are part of the filter chain, which is a series of filters that are executed in a specific order to perform various security-related tasks.
The AbstractAuthenticationProcessingFilter
is a base class for filters that handle authentication in Spring Security. It is designed to simplify the creation of custom authentication filters by providing common functionality needed for processing authentication requests.
Its key Responsibilities are:
- Processing Authentication Requests: It intercepts requests that require authentication.
- Extracting Credentials: It extracts authentication credentials (like username and password) from the request.
- Delegating Authentication: It delegates the authentication process to the configured
AuthenticationManager
.
The key methods of this class are :
- doFilter
The doFilter
method is overridden to handle requests.
This code snipped is taken from the open source code of spring security.
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Check if the request matches the URL pattern
if (requiresAuthentication(request, response)) {
// Process authentication
Authentication authResult = attemptAuthentication(request, response);
if (authResult == null) {
// If authentication is not successful, return
return;
}
// Successful authentication
successfulAuthentication(request, response, chain, authResult);
} else {
// If not an authentication request, proceed with the chain
chain.doFilter(request, response);
}
}
- attemptAuthenticatication
An abstract method that needs to be implemented by filters implementing the AbstractAuthenticationProcessingFilter
. Its job is to Performs actual authentication.
Suppose that we are handling a username and password authentication mecanisme, an example implementaion may be like this:
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
// Extract credentials (e.g., username and password)
String username = getUserNameFromRequest(request);
String password = getPasswordFromRequest(request);
// Create an authentication token
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
// Delegate authentication to the AuthenticationManager configured
return this.getAuthenticationManager().authenticate(authRequest);
}
- successfulAuthentication
this method is called if the authentication is successfull.
This is a code snippet taken from the open source code of spring security
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//...
//setting the authentication on the security context (meaning the request)
//is now authenticated
SecurityContextHolder.getContext().setAuthentication(authResult);
//...
}
Some out of the box implementations of this class are:
- UsernamePasswordAuthenticationFilter:
processes authentication requests using a username and password. It is typically used in form-based login scenarios.
- BasicAuthenticationFilter:
handles HTTP Basic Authentication, which involves sending the username and password in the Authorization
header of HTTP requests.
SecurityContextHolder: Managing the SecurityContext
Helper class that serves as a container for holding the SecurityContext of a running thread. It contains static methods used to manage the SecurityContext.
SecurityContext: Carrying Authentication Information
The SecurityContext includes crucial information about the authenticated user, and it acts as a central repository for accessing authentication details throughout the request-response lifecycle.
The SecurityContext
is retrieved from the SecurityContextHolder
and encapsulates the Authentication
object representing the currently authenticated user (also called the principal). This means that if you need to get any info (username, authorities, etc) of the current logged in user, you’ll need to get the SecurityContext first.
If the SecurityContext contains a non null Authentication value, then the current user/request is authenticated.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
// User is authenticated
// Access authentication details
String username = authentication.getName();
}
Authentication: Representing User Credentials
The Authentication
interface is a key player in the Spring Security authentication flow.
The Authentication
plays two key roles:
- As an Input to the Authentication Process: It is used to store the credentials (such as username and password) provided by the user attempting to authenticate (input to the
AuthenticationManager
) - As an Output from the Authentication Process: It holds the authenticated principal (user details) and their granted authorities (roles/permissions) after the user has been successfully authenticated.
An example code of how to get the username of the authenticated user:
// Get the security context
SecurityContext securityContext = SecurityContextHolder.getContext();
// Get the authentication object
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
// Get the principal (the authenticated user)
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
String username = userDetails.getUserName();
System.out.println("Username: ", username);
}
}
GrantedAuthority: Defining User Authorities
Authorities, such as roles or scopes, are represented by the GrantedAuthority
interface. These authorities are attached to the Authentication
object and are used to define what actions or resources a user is allowed to access within the application.
An example code to show how to get the list of granted authorities of the current logged in user:
// Get the security context
SecurityContext securityContext = SecurityContextHolder.getContext();
// Get the authentication object
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
// Get the authorities
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// Convert authorities to a list of strings
List<String> authoritiesList = authorities
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
}
AuthenticationManager: Orchestrating Authentication Flow
The AuthenticationManager
is a pivotal component that defines how authentication is performed in Spring Security. It acts as an API that Spring Security filters use to authenticate users.
This is an interface (so an implementation must be provided to perform the authentication) with an authenticate method.
Authentication authenicate(Authentication authentication);
The input parameter Authentication
object contains the credentials provided by the user (such as username and password).
The returned Authentication
object (if authentication is successfull) contains the authenticated principal (typically the user details) and their granted authorities (roles/permissions).
ProviderManager: Default Implementation of AuthenticationManager
ProviderManager
is the most common implementation of the AuthenticationManager
.
It manages a collection of AuthenticationProvider
instances, each responsible for a specific type of authentication.
During the authentication process, ProviderManager
iterates through these providers to find one that can authenticate the given Authentication
object and delegates the authentication to it.
spring security source code:
class ProviderManager implements AuthenticationManager {
private List<AuthenticationProvider> providers = Collections.emptyList();
//Construct a ProviderManager using the given AuthenticationProviders
ProviderManager(List<AuthenticationProvider> providers) {
//...
}
Authentication authenticate(Authentication authentication) {
//...
Class<? extends Authentication> toTest = authentication.getClass();
Authentication result = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
} catch (AuthenticationException e) {
//...
}
//...
}
}
}
When a user attempts to authenticate (e.g., by logging in), the ProviderManager
iterates through its list of providers registrated with it and it delegates the authentication request to the first provider that supports the authentication type (e.g., username/password, token-based, etc.).
AuthenticationProvider: Perform Specific Authentication
An AuthenticationProvider
is used by the ProviderManager
to perform a specific type of authentication, such as database authentication, LDAP authentication, or custom authentication mechanisms.
This is an interface that defines the contract for components responsible for validating user credentials.
So, developers can implement custom AuthenticationProvider
instances to integrate Spring Security with their authentication logic.
This interface has many OOTB implementations, the most common is DaoAuthenticationProvider (we’ll talk about it later):
- Retrieves user details from a
UserDetailsService
(usually backed by a database). - Validates credentials (e.g., username and password).
You can also provide your own implemenatation of AuthenticationProvider
if you need to control the authentication logic:
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
final String name = authentication.getName();
final String password = authentication.getCredentials().toString();
// Authenticate against third-party and return an Authentication object
return authenticateAgainstThirdPartyAndGetAuthentication(name, password);
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
The AuthenticationProvider
interface has only 2 methods: authenticate and supports.
- authenticate: takes an
Authentication
(the we saw earlier) object as input. This object encapsulates the user's credentials (typically username and password) to be validated. The provider performs the verification logic and returns a fully populatedAuthentication
object containing the authenticated user's details (username, authorities) if successful. If verification fails, it throws anAuthenticationException
- supports: This method allows the provider to indicate if it can handle the specific type of
Authentication
object presented. This helpsProviderManager
efficiently delegate authentication attempts. (in the example, the CustomAuthenticationProvider is able to handle theUsernamePasswordAuthenticationToken
which is a type of authentication that holds a username and password as the name says)
DaoAuthenticationProvider
The DaoAuthenticationProvider
is an OOTB implementation of the AuthenticationProvider
interface in Spring Security. It is responsible for authenticating users based on credentials stored in a data access object (DAO), such as a database.
the key responsibility of this class is to authenticate the user by comparing the its credentials (sent with the http request) with the credentials stored in a database or other datasource.
The authenticate
method is the core of the authentication process. It takes an Authentication
object (typically UsernamePasswordAuthenticationToken
) as input, retrieves user details from the UserDetailsService
, compares the provided password with the stored password, and returns an authenticated Authentication
object if successful.
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Retrieve user details from UserDetailsService
UserDetails user = userDetailsService.loadUserByUsername(authentication.getName());
// Compare passwords
if (!passwordEncoder.matches(authentication.getCredentials().toString(), user.getPassword())) {
throw new BadCredentialsException("Invalid credentials");
}
// Authentication successful
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
The DaoAuthenticationProvider
uses the UserDetailsService
to get the details about a username (sent in the request credentials).
The UserDetailsService
is a core interface in Spring Security. It is used to retrieve user-related data. It has a single method, loadUserByUsername(String username)
, which is used to look up user information based on the username provided.
When the DaoAuthenticationProvider
retrieves information from the database about the username sent in the request, it compares the password provided in the request with the password associated with that username in the database.
UserDetailsService:
The UserDetailsService
is a core interface in Spring Security. It is used to retrieve user-related data (such as username, password, authorities).
It has a single method, loadUserByUsername(String username)
, which is used to look up user information based on the username provided.
This interface is typically used by DaoAuthenticationProvider
to load user details during the authentication process.
It has one key method, which is loadUserByUsername:
The loadUserByUsername
method takes a username as input and returns a UserDetails
object. This UserDetails
object contains the user’s information, which Spring Security uses for authentication and authorization.
8. Request Credentials with AuthenticationEntryPoint
The AuthenticationEntryPoint
is an interface in Spring Security that defines a single method, commence
, which is invoked when a user tries to access a resource that requires authentication but they are not authenticated.
It is used in situations where:
- A client (user or application) makes a request to a protected resource.
- The client has not provided any credentials, or the provided credentials are invalid.
- Spring Security intercepts the request and determines that the client needs to be authenticated.
In such situations you may wanna for example:
- Redirect the client to a login page (common for web applications).
- Send an HTTP response with a status code and headers that prompt the client to provide credentials (common for APIs).
You can create custom implementations of the AuthenticationEntryPoint
interface to define what should happen when an unauthenticated request is received. For example, in a RESTful API, you might want to return a 401 Unauthorized status code with a JSON error message.
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest req, HttpServletResponse res,
AuthenticationException authException) throws IOException {
// Set the response status to 401 Unauthorized
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// Set the response content type to application/json
response.setContentType("application/json");
// Write a JSON message to the response body
response.getWriter().write("{\"error\": \"Unauthorized access - please provide valid credentials.\"}");
}
}