Spring Security 6.1 Deep Dive: Architecture, Filters, and Authentication Explained
This article provides a comprehensive analysis of Spring Security 6.1, covering its core architecture, the role of FilterChainProxy, detailed authentication and authorization flows, key interfaces such as SecurityFilterChain, AuthenticationManager, and practical code examples to help developers understand and debug the framework.
Spring Security is a powerful, highly customizable authentication and access‑control framework that protects against common attacks (CSRF, XSS, MITM, etc.) and offers features like password encoding, LDAP, session management, JWT, and OAuth 2.0. Because of its richness and extensibility, upgrading can be disruptive, and many newcomers encounter "nothing works" after adding the starter.
Basic Idea of Security in a Java Web Application
Security checks should be placed at the entry point of every request, i.e., before the @Controller. In Jakarta EE the Filter mechanism satisfies this requirement, and Spring Security implements its own Filter chain (via FilterChainProxy) that runs before the servlet dispatches the request.
The core of Spring Security is a single Filter that delegates to a chain of specialized security filters such as CsrfFilter, AuthenticationFilter, and AuthorizationFilter. Each filter handles a specific concern, keeping the overall design modular.
FilterChainProxy and SecurityFilterChain
When the application starts, the Spring Security Starter registers a DefaultSecurityFilterChain that contains about 15 default filters. The log below shows the chain composition:
2023-07-12T10:05:23.168+08:00 INFO 680540 --- [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@46e3559f, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3b83459e, org.springframework.security.web.context.SecurityContextHolderFilter@26837057, org.springframework.security.web.header.HeaderWriterFilter@2d74c81b, org.springframework.security.web.csrf.CsrfFilter@3a17b2e3, org.springframework.security.web.authentication.logout.LogoutFilter@5f5827d0, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4ed5a1b0, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3b332962, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32118208, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@67b355c8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@991cbde, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@dd4aec3, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@414f87a9, org.springframework.security.web.access.ExceptionTranslationFilter@59939293, org.springframework.security.web.access.intercept.AuthorizationFilter@f438904]Each SecurityFilterChain implements the SecurityFilterChain interface, which defines two methods:
public interface SecurityFilterChain extends java.io.Serializable {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}The matches method decides whether the chain applies to the current request, while getFilters returns the ordered list of filters that will be invoked.
DelegatingFilterProxy
The servlet container sees only a single DelegatingFilterProxy bean named springSecurityFilterChain. At runtime it looks up the actual filter bean from the Spring WebApplicationContext and delegates the doFilter call, bridging the servlet world and the Spring bean world.
Authentication (Identity Verification)
Spring Boot’s default security configuration enables form‑login and HTTP‑Basic authentication. The essential filter for username‑password login is UsernamePasswordAuthenticationFilter, whose doFilter method works as follows:
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (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);
Authentication result = this.getAuthenticationManager().authenticate(authRequest);
// on success the result is stored in SecurityContext
// on failure an AuthenticationException is thrown
chain.doFilter(request, response);
}The filter first checks that the request is a POST to the login URL, extracts credentials, builds an UsernamePasswordAuthenticationToken, and hands it to the AuthenticationManager.
The AuthenticationManager interface has a single method:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}Its default implementation is ProviderManager, which holds a list of AuthenticationProvider instances. For standard username‑password authentication the provider is DaoAuthenticationProvider, which uses a UserDetailsService to load user data and a PasswordEncoder to verify the password.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}Typical providers: DaoAuthenticationProvider – username/password via
UserDetailsService JwtAuthenticationProvider– JWT token verification
After successful authentication the Authentication object (usually a UsernamePasswordAuthenticationToken) is stored in the SecurityContext via SecurityContextHolder.getContext().setAuthentication(...). Subsequent filters (e.g., authorization) retrieve it from the context.
Authorization (Access Control)
Authorization is enabled by calling http.authorizeHttpRequests(...). This registers an AuthorizationFilter as the last filter in the chain. Its doFilter method delegates to an AuthorizationManager:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
try {
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
chain.doFilter(request, response);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext");
}
return authentication;
}The manager (commonly AuthorityAuthorizationManager) checks the authorities of the current Authentication against the required permissions for the request. Example DSL configuration:
@Bean
static SecurityFilterChain mySecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(requests ->
requests
.requestMatchers("/admin").hasAuthority("ROLE_ADMIN")
.requestMatchers("/hello").hasRole("USER")
.anyRequest().authenticated()
);
http.formLogin(withDefaults());
return http.build();
}Methods like hasAuthority and hasRole ultimately create an AuthorityAuthorizationManager that compares the required authority with the collection returned by Authentication.getAuthorities(). The hasRole shortcut automatically prefixes the role name with ROLE_.
Customising the Stack
Most components are replaceable: UserDetailsService – replace the in‑memory default with a database‑backed JdbcUserDetailsManager or a custom implementation. PasswordEncoder – choose BCryptPasswordEncoder, Argon2PasswordEncoder, scrypt, etc., according to security policy. AuthenticationProvider – implement a custom provider that calls an external authentication API. AuthorizationManager – supply a bespoke manager for complex permission logic.
Debugging Tips
When a request is denied, start debugging at the filter that performs the check:
Authentication problems – set a breakpoint in UsernamePasswordAuthenticationFilter.doFilter (or any custom authentication filter).
Authorization problems – debug AuthorizationFilter.doFilter.
The ExceptionTranslationFilter catches AuthenticationException and AccessDeniedException, delegating to an AuthenticationEntryPoint or an AccessDeniedHandler respectively. Enabling DEBUG logging for org.springframework.security shows the full filter list (search for "Will secure any request with").
Key Interfaces and Classes
SecurityFilterChain– matches request and provides ordered filters. FilterChainProxy – selects the appropriate SecurityFilterChain for a request. DelegatingFilterProxy – bridges servlet container and Spring bean. AuthenticationManager / ProviderManager – orchestrates authentication providers. AuthenticationProvider – concrete authentication logic (e.g., DaoAuthenticationProvider). Authentication – token representing authentication result. AuthorizationManager / AuthorityAuthorizationManager – evaluates access decisions.
Architecture Diagram
The diagram shows how an incoming HTTP request passes through DelegatingFilterProxy, then through the selected SecurityFilterChain (a series of filters), ending with the application controller once authentication and authorization succeed.
Conclusion
This article dissected the core of Spring Security 6.1, focusing on the filter‑based architecture, the authentication pipeline (from UsernamePasswordAuthenticationFilter to ProviderManager), and the authorization flow via AuthorizationFilter and AuthorizationManager. Understanding these building blocks enables developers to customise security, replace default components, and efficiently debug issues.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
