Unveiling Spring Security 6: Architecture, Filters, and Authentication Deep Dive

This article provides a comprehensive analysis of Spring Security 6's architecture, explaining how the framework uses a chain of servlet Filters, the DelegatingFilterProxy, and the SecurityFilterChain to implement authentication, authorization, and protection against common attacks, while also offering practical debugging tips and configuration guidance.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
Unveiling Spring Security 6: Architecture, Filters, and Authentication Deep Dive

Introduction

Spring Security is a powerful, highly customizable authentication and access‑control framework that protects against clickjacking, CSRF, XSS, MITM and many other attacks. Its richness and configurability make upgrades challenging, especially for newcomers who often encounter broken pages after adding the starter.

Basic Architecture Overview

The core idea is that all security checks are performed by a Filter placed at the very beginning of the request processing pipeline, before any controller logic. In a typical Spring MVC application there is a single DispatcherServlet; the security Filter runs before it.

The framework builds a SecurityFilterChain composed of multiple specialized Filters such as CsrfFilter, AuthenticationFilter, AuthorizationFilter, DefaultLoginPageGeneratingFilter, and others. Each Filter handles a specific concern, and the chain can be customized by adding or removing Filters.

Spring Security architecture diagram
Spring Security architecture diagram

Filter Chain Mechanics

When the application starts, Spring Boot registers a FilterChainProxy bean. This proxy holds a list of SecurityFilterChain objects, each associated with a set of request matchers (e.g., URL patterns). The proxy iterates over the chains, selects the first one whose matches(request) method returns true, and delegates the request to that chain’s Filters.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    List<Filter> filters = getFilters(request);
    for (Filter f : filters) {
        f.doFilter(request, response, chain);
    }
}

private List<Filter> getFilters(HttpServletRequest request) {
    for (SecurityFilterChain chain : this.filterChains) {
        if (chain.matches(request)) {
            return chain.getFilters();
        }
    }
    return null;
}

The DelegatingFilterProxy bridges the servlet container and the Spring context: it looks up a bean named springSecurityFilterChain and forwards doFilter calls to it.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    Filter delegate = this.delegate;
    if (delegate == null) {
        synchronized (this.delegateMonitor) {
            delegate = this.delegate;
            if (delegate == null) {
                WebApplicationContext wac = findWebApplicationContext();
                delegate = initDelegate(wac);
            }
            this.delegate = delegate;
        }
    }
    delegate.doFilter(request, response, chain);
}

Authentication Flow

For form‑login authentication Spring Security registers a UsernamePasswordAuthenticationFilter. Its doFilter method extracts username and password from the request, builds a UsernamePasswordAuthenticationToken, and hands it to the AuthenticationManager (usually a ProviderManager).

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && !"POST".equals(request.getMethod())) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    String username = obtainUsername(request);
    String password = obtainPassword(request);
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
}

The ProviderManager iterates over configured AuthenticationProvider instances (e.g., DaoAuthenticationProvider) until one supports the token type. The provider retrieves user details via UserDetailsService, checks the password with a PasswordEncoder, and returns an authenticated token containing authorities.

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UserDetails user = userDetailsService.loadUserByUsername(((UsernamePasswordAuthenticationToken) authentication).getName());
    if (!passwordEncoder.matches(((UsernamePasswordAuthenticationToken) authentication).getCredentials(), user.getPassword())) {
        throw new BadCredentialsException("Bad credentials");
    }
    return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
}

The successful token is stored in SecurityContextHolder.getContext().setAuthentication(...), making it available for later authorization checks.

Authorization Flow

Authorization is triggered by HttpSecurity.authorizeHttpRequests(...). This registers an AuthorizationFilter as the last Filter in the chain. The filter obtains the current Authentication from the security context and delegates the decision to an AuthorizationManager (commonly AuthorityAuthorizationManager).

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
    if (decision != null && !decision.isGranted()) {
        throw new AccessDeniedException("Access Denied");
    }
    chain.doFilter(request, response);
}

private Authentication getAuthentication() {
    Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication();
    if (auth == null) {
        throw new AuthenticationCredentialsNotFoundException("No Authentication object in SecurityContext");
    }
    return auth;
}

Typical DSL calls such as .requestMatchers("/admin").hasAuthority("ROLE_ADMIN") create an AuthorityAuthorizationManager that checks whether the authenticated principal’s GrantedAuthority collection contains the required authority. .hasRole("USER") is a shortcut that prefixes the role with ROLE_.

public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
    return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
}

public static AuthorityAuthorizationManager hasAuthority(String authority) {
    Assert.notNull(authority, "authority cannot be null");
    return new AuthorityAuthorizationManager<>(authority);
}

Roles and authorities are both simple strings; the distinction is conceptual—authorities represent fine‑grained permissions (e.g., ADD_USER), while roles are higher‑level groupings that may map to multiple authorities.

Configuration Highlights

Spring Boot’s auto‑configuration registers about 15 default Filters, including CSRF, session management, basic auth, form login, and exception handling.

From Spring Security 6.0 onward, the old FilterSecurityInterceptor and WebSecurityConfigurerAdapter are removed; the new DSL uses AuthorizationFilter and lambda‑based configuration.

Custom Filters can be added via http.addFilter(...) or by implementing a SecurityConfigurerAdapter and applying it with http.apply(...).

Debugging Tips

When a request is denied, start debugging at the Filter that produced the exception:

Authentication failures: set a breakpoint in UsernamePasswordAuthenticationFilter.doFilter or the specific AuthenticationProvider you use.

Authorization failures: debug AuthorizationFilter.doFilter and inspect the AuthorizationDecision.

To see the full list of active Filters, enable DEBUG logging for org.springframework.security.web.DefaultSecurityFilterChain which prints the chain at startup.

Conclusion

The article dissected Spring Security’s core architecture, focusing on how the FilterChainProxy, DelegatingFilterProxy, authentication Filters, and authorization Managers collaborate to provide a flexible, extensible security solution. Understanding these building blocks enables developers to customize authentication mechanisms, replace default components, and troubleshoot security‑related issues effectively.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootAuthenticationAuthorizationFilter Chainspring-security
IT Architects Alliance
Written by

IT Architects Alliance

Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.