askvity

How Does Spring Security Work Internally?

Published in Spring Security Internals 5 mins read

Spring Security secures your application by intercepting requests and applying security constraints.

At its core, Spring Security operates through a series of standard Servlet Filter implementations, arranged in a chain. When a request arrives at your web application, it doesn't go straight to your controller or resource handler.

The Foundation: The Filter Chain

As stated, when a client sends a request to access a resource on a server embedded with Spring Security, the request undergoes interception by a component known as a filter. This means that the request doesn't hit the server directly, rather it goes to the filter first for authentication.

This interception is managed by Spring Security's main entry point, the FilterChainProxy. Think of the FilterChainProxy as the dispatcher for security requests. It determines which specific SecurityFilterChain should handle the incoming request based on the request's URL and other properties.

A SecurityFilterChain is essentially a list of individual Filter instances, each responsible for a specific security concern, such as:

  • Authentication: Verifying the user's identity.
  • Authorization: Determining if the authenticated user has permission to access the requested resource.
  • Session Management: Handling user sessions.
  • CSRF Protection: Protecting against Cross-Site Request Forgery attacks.
  • Header Security: Adding security-related headers to responses.

The request passes through each filter in the selected SecurityFilterChain in order. Each filter can perform actions like checking credentials, enforcing access rules, modifying the request or response, or even terminating the request processing (e.g., if authentication fails or access is denied).

Key Components

Beyond the filters themselves, several other key components collaborate within the filter chain:

  • AuthenticationManager: This is the central interface for authentication. It receives an Authentication object (representing a user's credentials) and attempts to authenticate it. It often delegates the actual authentication work to configured AuthenticationProviders.
  • AuthenticationProvider: Performs the actual authentication logic. Examples include providers for database lookups (DaoAuthenticationProvider), LDAP, OAuth2, etc.
  • UserDetailsService: An interface used by many AuthenticationProviders (like DaoAuthenticationProvider) to load user-specific data (username, password, authorities) for authentication and authorization checks.
  • SecurityContextHolder: A central storage location for the current user's security context, which includes the Authentication object representing the authenticated user and their granted authorities. It's typically stored in a ThreadLocal, making the security context readily available anywhere in your application code.
  • GrantedAuthority: Represents a permission or role assigned to a user. Used during the authorization process.
  • AccessDecisionManager: The central interface for authorization. It decides whether a user (represented by their Authentication and requested secure object, like a URL or method) is allowed to proceed. It typically delegates to configured AccessDecisionVoters.
  • AccessDecisionVoter: Votes on whether a user should be granted access. Different voters can implement different access control strategies (e.g., checking if the user has a specific role, evaluating an expression).

The Security Process Flow (Simplified)

Here’s a basic step-by-step view of how a request is processed:

  1. A client sends a request to a protected resource (e.g., /admin/dashboard).
  2. The request first hits the FilterChainProxy.
  3. FilterChainProxy matches the request URI to a specific SecurityFilterChain.
  4. The request is passed down the list of Filters configured in that SecurityFilterChain.
  5. An Authentication Filter (e.g., UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter) intercepts the request. If it finds credentials (like username/password), it creates an Authentication object and passes it to the AuthenticationManager.
  6. The AuthenticationManager delegates to an appropriate AuthenticationProvider.
  7. The AuthenticationProvider uses a UserDetailsService to load the user's details and verifies the credentials.
  8. If authentication is successful, the AuthenticationProvider returns a fully populated Authentication object containing the user's principal and authorities.
  9. The successful Authentication object is stored in the SecurityContextHolder. The user is now authenticated.
  10. The request continues down the filter chain.
  11. An Authorization Filter (e.g., AuthorizationFilter introduced in Spring Security 5.5+) intercepts the request. It consults the AccessDecisionManager.
  12. The AccessDecisionManager uses AccessDecisionVoters to evaluate if the authenticated user (based on the Authentication object in the SecurityContextHolder and the requested resource) has the necessary GrantedAuthority to access the resource.
  13. If authorization is granted, the request proceeds down the rest of the filter chain and eventually reaches the intended resource handler (e.g., your controller method).
  14. If authentication or authorization fails at any point, the corresponding filter will typically stop the processing chain and return an error response (e.g., 401 Unauthorized, 403 Forbidden).

This layered approach, centered around the filter chain and collaborating components, provides the flexibility and power that makes Spring Security a robust framework for securing web applications.

Related Articles