Published on

Discussing Spring security servlet authentication architecture (Main classes/interfaces)

Authors
  • avatar
    Name
    Alae Touba
    Twitter

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:

  1. Processing Authentication Requests: It intercepts requests that require authentication.
  2. Extracting Credentials: It extracts authentication credentials (like username and password) from the request.
  3. 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:

  1. 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)
  2. 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 populated Authentication object containing the authenticated user's details (username, authorities) if successful. If verification fails, it throws an AuthenticationException
  • supports: This method allows the provider to indicate if it can handle the specific type of Authentication object presented. This helps ProviderManager efficiently delegate authentication attempts. (in the example, the CustomAuthenticationProvider is able to handle the UsernamePasswordAuthenticationToken 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.\"}");
    }
}