Spring Security Default Security Filter Chain

From parent article

Setting Up Spring Security

To get started with Spring Security, add the following dependency to pom.xml

<dependencies>
    ...
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    ...
</dependencies>        

After adding the dependency, run the following command to start your application:

./mvnw spring-boot:run        

Once the application starts, Spring will secure the application with the default security filter chain .

When your application start up it will log your Default Security Filter Chain along with the generated password.

By default, when you access any endpoint, you will be presented with a login page.

The default username is user, and the password is generated and logged during application startup.

To understand how the default security setup works, let’s take a look at the default security filters in the chain.

Default Security Filter Chain

Security Filters

The FilterChainProxy stores the SecurityFilterChain in a list called additionalFilters, a counter named currentPosition is used to manage where along the chain the request is being processed. In the image above, the currentPosition is 3 so the request is being processed in the SecurityContextHolderFilter.

We will focus on

  • UsernamePasswordAuthenticationFilter
  • BasicAuthenticationFilter

[Optional] You can read more about the other security filters here:

Username Password Authentication Flow


Retrieving Security Context

When an incoming request enters the Security Filter Chain and reaches the SecurityContextHolderFilter it will call the SecurityContextRepository to retrieve any existing SecurityContext associated with the Cookie JSESSIONID.

JSESSIONID is a cookie used to track user sessions. When a user logs in, their authenticated state is represented by an Authentication object, which is encapsulated in a SecurityContext stored in memory by the SecurityContextRepository. For subsequent requests in the same session, this Authentication object is retrieved sparing the user the extra work of re-authenticating.

Since the user hasn’t been authenticated in this session, an empty Security Context is created stored in in the SecurityContextHolder.

The SecurityContextHolder is a thread local store for the SecurityContext, meaning that the SecurityContext and its Authentication object is global to the current request meaning that it can be access anywhere during the request lifecycle and is isolated from other requests.

Getting Redirected to Login Page

When the request reaches the ExceptionTranslationFilter, which redirects unauthenticated requests to the login page.

The login page is a JSP page generated by the DefaultLoginPageGenerating filter.

A JSP page is a server generated page that dynamically creates HTML content to display in a web browser.

Authentication with the UsernamePasswordAuthenticationFilter

When you sign into the login page, it will make a POST request to the /login endpoint with the user’s username and password.

The POST request to the /login endpoint will be intercepted by the UsernamePasswordAuthenticationFilter.

The filter will extract the username and password from the request and created an unauthenticated UsernamePasswordAuthenticationToken(implementation of Authentication) and pass the token to the authenticate method of the AuthenticationManager.

Authentication Architecture

Unimplemented

When an Authentication object is processed by the AuthenticationManager's authenticate method, it delegates the authentication to an appropriate AuthenticationProvider. The AuthenticationProvider extracts the username and password from the Authentication object, retrieves the corresponding UserDetails (which includes the stored password), and encodes the provided password using a PasswordEncoder. The authentication is successful if the encoded password matches the stored password in the UserDetails.

  • AuthenticationManager: Central interface for handling authentication requests.
  • AuthenticationProvider: Component that performs the actual authentication by processing credentials.
  • PasswordEncoder: Tool for encoding and validating passwords securely.
  • UserDetailsService: Service that loads user-specific data from a data source.
  • UserDetails: Object representing user information, including username, password, and roles.

The default configuration for Spring Security, the AuthenticationManager is implemented as ProviderManager, the PasswordEncoder is implemented as DelegatingPasswordEncoder and the UserDetailsService is implemented as InMemoryUserDetailsManager.

The ProviderManager will decide which which implementation of AuthenticationProvider to delegate authentication to by looping over all available providers. It selects the provider that supports the UsernamePasswordAuthenticationToken which is the implementation of the Authentication object that is passed from the UsernamePasswordAuthentication filter.

Since the abstract class AbstractDetailsAuthenticationProvider supports UsernamePasswordAuthenticationToken the ProviderManager will delegate authentication to the DaoAuthenticationProvider.


public abstract class AbstractUserDetailsAuthenticationProvider
  implements AuthenticationProvider, InitializingBean, MessageSourceAware {
 ....
  @Override
  public boolean supports(Class<?> authentication) {
    return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
  }
  ...
}        

We will authenticate the Authentication token encoding the password and compared it with the encoded password in the UserDetails object retrieved from the datastore.

When the request has been authenticated an authenticated Authentication object will be created using the username and authorities from the UserDetails object, for safety consideration we will not store the password.

Once the SecurityContextHolder contains the authenticated Authentication with the proper authorities it has access to the relevant controller.

Subsequent Requests of Authenticated Session

When the response being returned to the client reaches the SecurityContextPersistenceFilter, the SecurityContext is stored by the SecurityContextRepository and then cleared from the SecurityContextHolder. This is important because:

  • Storing the SecurityContext allows it to be reused for future requests in the same session, eliminating the need for re-authentication.
  • Clearing the SecurityContextHolder prevents sensitive information from the SecurityContext from leaking to other threads or requests from different sessions.

When a subsequent request is made within the same session, the SecurityContextHolderFilter retrieves the stored authenticated SecurityContext from the SecurityContextRepository using the JSESSIONID.

Once the authenticated Authentication object is loaded into the SecurityContextHolder, the request is granted access to the relevant controller.

Basic Authentication Flow


Consider an incoming request with the following authorization header.

Authorization: Basic <encoded token>        

The <encoded token> is the Base64 encoded string of <username>:<password>

Similar to the Username Password Authentication flow, an incoming request reaches the SecurityContextHolderFilter which retrieves the SecurityContext of the current session from SecurityContextRepository, since the user has not previously been authenticated an empty SecurityContext is stored in the SecurityContextHolder.

Since the request contains the Authorization header with the Basic scheme, it will trigger the BasicAuthenticationFilter.

The filter will extract the username and password from the encoded token, create an unauthenticated Authentication token, and delegate it to the ProviderManager to perform authentication, similar to the process used in the Username Password Authentication flow.

Once the request has been authenticated it will have access to the controller.

Similar to the Username Password Authentication when the response reach the SecurityContextHolderFilter, the SecurityContextRepository will store the SecurityContext for subsequent request in the same session and clear the SecurityContext in the SecurityContextHolder.

A subsequent request will have access to the authenticated SecurityContext, eliminating the need for re-authentication.

Drawback to the Username Password and Basic Authentication flow

We will no longer use the default security filter chain because it is tightly coupled with the Spring MVC framework, which assumes a server-generated frontend closely integrated with backend REST APIs. This setup relies on server-managed sessions (JSESSIONID) to maintain authentication state, making it unsuitable for hosting REST APIs in a modern web application where the backend is decoupled from the frontend, particularly when using frameworks like React or Vue.

Another significant drawback of the default security filter chain emerges when scaling the application across multiple servers. Since JSESSIONID is stored in memory on a single server, a user who logs in on one server might be routed to another server by a load balancer. If the session is managed in-memory, the new server won’t recognize the user's session, causing authentication failures. This issue highlights the limitations of relying on server-managed sessions in a distributed environment, underscoring the need for a more stateless, scalable approach to authentication.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了