Understanding the Architecture of Spring Security

Spring Security is a robust framework that provides authentication, authorization, and protection against common attacks in java applications. Its architecture is designed to be highly extensible, making it suitable for a wide range of security requirements. In this blog post, we will delve into the key component and overall architecture of spring security.

Overview of Spring Security

Spring Security’s architecture is composed of several layers that handle different aspects of security:

  1. Core Security Components
  2. Authentication and Authorization
  3. Filters and Interceptors
  4. Security Context
  5. Pluggable Extensions

Each of these components plays a crucial role in providing comprehensive security features.

Core Security Components

  1. Security Context
    • The SecurityContext holds the authentication information (represented by Authentication objects). It is the heart of Spring Security, ensuring that authentication information is accessible throughout the application.
  2. Security Context Holder
    • SecurityContextHolder is a helper class that provides access to the SecurityContext. It uses a thread-local variable to store the context, making it available throughout the processing of a request.
  3. Authentication
    • The Authentication object contains the principal (user) information and their credential. It also holds granted authorities, which are permissions or roles assigned to the user.
  4. UserDetailsService
    • The UserDetailsService interface is used to retrieve user-related data. It has a single method, `loadUserByUsername`, which loads a user based on the username.

Authentication and Authorization

  1. AuthenticationManager
    • The AuthenticationManager is the main strategy interface for authentication. It has a single method, authenticate, which processes an Authentication request. If successful, it returns a fully authenticated object, otherwise, it throws an exeception.
  2. ProviderManager
    • ProviderManager is the most commonly used implementation of AuthenticationManager. It delegates authentication requests to a list of AuthenticationProvider
  3. AuthenticationProvider
    • An AuthenticationProvider performs authentication logic. If it cannot authenticate, it returns null, allowing other providers to try. This chain of responsibility ensures flexibility in handling various authentication mechanimsm.
  4. AccessDecisionManager
    • AccessDecisionManager makes authorization decisions. It considers the Authentication object, the secured object, and the required collection of ConfigAttribute. It has three decision methods: decide, supports, and supports.
  5. AccessDecistionVoter
    • An AccessDecisionVoter is used by AccessDecisionManager to make a final decision on authorization. Voters either grant or deny access based on specific logic

Filters and Interceptors

Spring Security uses a chain of filters to process security-related tasks. These filters ensure that security checks are performed at appropriate stages in the request lifecycle.

  1. SecurityFilterChain
    • The SecurityFilterChain is a collection of filters that apply to specific URL patterns. Each filter in the chain performs a specific task, such as authentication or CSRF protection.
  2. FilterSecurityInterceptor
    • FilterSecurityInterceptor is the last filter in chain. It enforces security on the request using AccessDecisionManager and SecurityMetadataSource.

Security Context

The security context is crucial for maintaining the authentication state across multiple requests.

  1. SecurityContextPersistenceFilter
    • SecurityContextPersistenceFilter manages the SecurityContext lifecycle. It loads the context at the beginning of a request and stores it at the end.
  2. AnonymousAuthenticationFilter
    • AnonymousAuthenticationFilter provides anonymous authentication for requests that don’t require explicit login. It assigns a default anonymous user with limited permissions.

Pluggable Extensions

Spring Security’s architecture is designed to be highly extensible. You can add custom logic at various points by implementing or extending key interfaces.

  1. Custom Authentication Providers
    • You can create custom AuthenticationProvider’s to handle specific authentication mechanisms.
  2. Custom UserDetailsService
    • Implementing a custom UserDetailsService allows you to fetch user data from different sources (database, LDAP..)
  3. Custom Filters
    • Creating custom filters can help you implement additional security measures, such as logging or rate limiting.

Spring Security Flow

The first user makes a request to the application, which then goes through Spring Security filters first. These filters are internal, each performing its own specific role in securing the application. Let’s delve into these filters one by one to understand how they work.

AuthorizationFilter

This filter restricts access to URLs using an AuthorizationManager. The function that performs this task within the filter is called doFilter.

@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
			throws ServletException, IOException {

		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;

		if (this.observeOncePerRequest && isApplied(request)) {
			chain.doFilter(request, response);
			return;
		}

		if (skipDispatch(request)) {
			chain.doFilter(request, response);
			return;
		}

		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
		try {
			AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
			this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
			if (decision != null && !decision.isGranted()) {
				throw new AccessDeniedException("Access Denied");
			}
			chain.doFilter(request, response);
		}
		finally {
			request.removeAttribute(alreadyFilteredAttributeName);
		}
	}

As you can see, if the URL you attempt to access is not authorized, a new exception will be thrown. This triggers the filter to move on to the next filter in the chain.

DefaultLoginPageGenerationFilter

This filter will generate login page for enter username and password

UsernamePasswordAuthenticationFilter

This filter is responsible for extracting the username and password from the request. It then creates an authentication object, specifically a UsernamePasswordAuthenticationToken. This class implements the Authentication interface, which defines the essential information for user authentication. After constructing the token, the filter passes it to the AuthenticationManager for verification in step 3, as shown in the code below.

@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
				password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}

AuthenticationManager

The AuthenticationManager is responsible for authenticating the request. It’s an interface with a single method called authenticate. This method takes an Authentication object (usually the UsernamePasswordAuthenticationToken created in the previous step) and attempts to verify its credentials. However, the AuthenticationManager itself doesn’t perform the actual authentication. Instead, it delegates this task to one or more AuthenticationProvider implementations.

We iterate through all available AuthenticationProvider implementations. If any one of them successfully authenticates the request, the authenticate function terminates. Now, let’s delve into a specific implementation: the DaoAuthenticationProvider.

DAOAuthenticationProvider

This is implementation of AuthenticationProvider that retrieves user information from a UserDetailService

InmemoryUserDetailsManager

InMemoryUserDetailsManager is an implementation of UserDetailsService that retrieves user information stored in memory, making it useful for development and testing purposes.

PasswordEncoder

When DaoAuthenticationProvider authenticates a user and retrieves user information from the UserDetailsService, it performs additional authentication checks. One crucial check involves verifying if the provided password matches the stored password. To perform this comparison securely, DaoAuthenticationProvider utilizes a PasswordEncoder.

That completes our overview of the core Spring Security authentication process. In upcoming blog posts, we’ll delve into customizing authentication logic and explore Spring Security in greater depth.

Conclusion

Spring Security provides a comprehensive and flexible framework for securing Java applications. Its architecture, with clearly defined components and extension points, allows developers to implement robust security measures to their specific needs. Understanding these core concepts and components is essential for leveraging the full potential of Spring Security.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top