Understanding how the Spring Security Filter Chain works is crucial for building secure and robust Java applications. It acts as a series of interceptors, each performing a specific security task before the request reaches your application’s endpoints. This chain is responsible for authenticating users, authorizing access to resources, protecting against common web vulnerabilities, and more. Think of it as a security checkpoint at every stage of your application’s request processing. Developers leveraging Spring Security benefit from its flexibility and extensibility, allowing them to tailor the filter chain to meet the precise security needs of their projects, ensuring that only authorized users can access protected resources. Mastering this mechanism is essential for any developer serious about application security, as it provides a solid foundation for creating secure and reliable web applications. The configuration and customization options are vast, letting you fine-tune security measures to match your specific requirements.
The Core Concepts of Spring Security Filter Chain
At its heart, the Spring Security Filter Chain is a sequence of javax.servlet.Filter instances that are executed in a specific order. Each filter in the chain is responsible for a particular security-related task. These tasks can range from authenticating users based on credentials to validating session integrity and preventing cross-site scripting (XSS) attacks. The order of these filters is paramount, as it dictates the sequence in which security checks are performed. A misconfigured filter chain can lead to vulnerabilities or unexpected behavior, highlighting the importance of understanding its inner workings.
The FilterChainProxy is a central component in Spring Security. It’s a Servlet filter that delegates to the configured list of Spring Security filters. This proxy intercepts all requests and applies the chain of filters. The FilterChainProxy determines which filters should be applied based on the request, and executes them in the order they are defined. This provides a centralized point for managing and controlling the security filters, ensuring consistency and simplifying configuration. The filters themselves are configured within the Spring application context.
Filters are added to the FilterChainProxy based on URL patterns or other request attributes. Spring Security provides a set of pre-built filters for common security tasks, such as authentication, authorization, and request forgery protection. However, you can also create your own custom filters to handle specific security requirements. According to OWASP, using a layered security approach, similar to the Spring Security Filter Chain, is a recommended best practice to defend against various threats. Source: OWASP Top Ten
Common Filters in a Spring Security Filter Chain
Several common filters typically appear in a Spring Security Filter Chain. UsernamePasswordAuthenticationFilter handles authentication based on username and password provided in a form. BasicAuthenticationFilter handles authentication based on HTTP Basic Authentication headers. SessionManagementFilter manages sessions, preventing concurrent logins and handling session fixation attacks. CsrfFilter protects against Cross-Site Request Forgery attacks by requiring a unique token in each request. Properly configuring these and other filters ensures your application is protected from common web vulnerabilities.
FilterSecurityInterceptor is another essential filter. It’s responsible for enforcing authorization rules. It uses an AccessDecisionManager to determine whether the current user has the necessary permissions to access the requested resource. The AccessDecisionManager consults a list of AccessDecisionVoter instances, each of which votes on whether access should be granted. This flexible architecture allows for complex authorization logic to be implemented. For example, you can configure the FilterSecurityInterceptor to allow access only to users with specific roles or to users who meet certain criteria.
Here are some key filters and their functions:
- UsernamePasswordAuthenticationFilter: Handles username/password authentication.
- BasicAuthenticationFilter: Handles HTTP Basic authentication.
- SessionManagementFilter: Manages user sessions and protects against session-based attacks.
- CsrfFilter: Protects against Cross-Site Request Forgery (CSRF) attacks.
- FilterSecurityInterceptor: Enforces authorization rules.
Configuring a Spring Security Filter Chain
Configuring a Spring Security Filter Chain can be done in several ways. One common approach is to use Java configuration with @EnableWebSecurity and @Configuration annotations. This allows you to define the filter chain in a programmatic and type-safe manner. Another approach is to use XML configuration, although this is becoming less common as Java configuration becomes the preferred method. Regardless of the approach, it’s important to understand the underlying principles and best practices for configuring the filter chain effectively. You can choose which URLs are secured and which are not, providing granular control over your application’s security.
To configure a basic Spring Security Filter Chain using Java configuration, you can create a class that extends WebSecurityConfigurerAdapter. Within this class, you can override the configure(HttpSecurity http) method to define the filter chain. This method allows you to specify which requests should be authenticated, which authentication mechanisms should be used, and which authorization rules should be enforced. For example, you can configure the filter chain to require authentication for all requests except for those to the /public endpoint. You can also configure custom login and logout pages, as well as various other security settings.
Here’s a simplified example of configuring the filter chain:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/").permitAll() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic(); } }
This configuration allows unauthenticated access to any URL starting with /public/, and requires authentication for all other URLs. It also enables form-based login and HTTP Basic authentication. Remember to properly encode passwords using a PasswordEncoder for security best practices.
Customizing the Filter Chain
You can customize the Spring Security Filter Chain by adding your own custom filters. This allows you to implement specific security requirements that are not covered by the pre-built filters. To add a custom filter, you can create a class that implements the javax.servlet.Filter interface and then add it to the filter chain using the addFilterBefore() or addFilterAfter() methods of the HttpSecurity object. For example, you might create a custom filter to log all incoming requests or to perform additional validation on request parameters. By strategically placing your custom filters within the chain, you can precisely control the order in which they are executed.
Understanding the Filter Chain Execution Flow
The execution flow of the Spring Security Filter Chain is sequential. When a request comes in, it first hits the FilterChainProxy. The FilterChainProxy then iterates through the configured filters, executing each one in order. Each filter has the opportunity to process the request and potentially modify it. If a filter decides that the request should not be processed further (e.g., because the user is not authenticated), it can interrupt the chain and return an error response. Otherwise, the filter passes the request on to the next filter in the chain. This continues until the request reaches the end of the chain and is finally processed by the application’s endpoint.
Understanding the order in which filters are executed is crucial. For example, the CsrfFilter should typically be placed before the FilterSecurityInterceptor, so that CSRF protection is enforced before authorization checks are performed. Similarly, the SessionManagementFilter should be placed early in the chain to ensure that session management is handled before any other security checks are performed. A well-designed filter chain ensures that all necessary security checks are performed in the correct order, minimizing the risk of vulnerabilities.
Here’s a simplified view of the filter chain execution flow:
- Request arrives at FilterChainProxy.
- FilterChainProxy iterates through the configured filters.
- Each filter processes the request and either:
- Continues the chain by calling filterChain.doFilter(request, response).
- Interrupts the chain by returning an error response.
- Request reaches the application endpoint (if the chain is not interrupted).
- Response is returned through the filter chain in reverse order.
The Spring Security Filter Chain is a series of filters that intercept and process incoming HTTP requests to provide security features like authentication, authorization, and protection against common web vulnerabilities. It’s crucial to understand how these filters work together and the order in which they are executed to build secure and reliable applications. You can learn more about the architecture on Spring’s official website. Source: Spring Security Project
- What is the purpose of the Spring Security Filter Chain?
- The Spring Security Filter Chain provides a series of filters that intercept and process HTTP requests to enforce security policies, such as authentication and authorization.
- How do I configure the Spring Security Filter Chain?
- You can configure the filter chain using Java configuration (with @EnableWebSecurity) or XML configuration, defining the order and behavior of the filters.
- Can I add custom filters to the Spring Security Filter Chain?
- Yes, you can add custom filters by implementing the javax.servlet.Filter interface and adding them to the chain using HttpSecurity configuration.
- What is the FilterChainProxy?
- The FilterChainProxy is a Servlet filter that delegates to the configured list of Spring Security filters, acting as the entry point for the filter chain.
Question & Answer :
I realize that Spring security build on chain of filters, which will intercept the request, detect (absence of) authentication, redirect to authentication entry point or pass the request to authorization service, and eventually let the request either hit the servlet or throw security exception (unauthenticated or unauthorized). DelegatingFitlerProxy glues these filters together. To perform their tasks, these filter access services such as UserDetailsService and AuthenticationManager.
Key filters in the chain are (in the order)
- SecurityContextPersistenceFilter (restores Authentication from JSESSIONID)
- UsernamePasswordAuthenticationFilter (performs authentication)
- ExceptionTranslationFilter (catch security exceptions from FilterSecurityInterceptor)
- FilterSecurityInterceptor (may throw authentication and authorization exceptions)
I’m confused how these filters are used. Is it that for the spring provided form-login, UsernamePasswordAuthenticationFilter is only used for /login, and latter filters are not? Does the form-login namespace element auto-configure these filters? Does every request (authenticated or not) reach FilterSecurityInterceptor for non-login url?
What if I want to secure my REST API with JWT-token, which is retrieved from login? I must configure two namespace configuration http tags, rights? One for /login with UsernamePasswordAuthenticationFilter, and another one for REST url’s, with custom JwtAuthenticationFilter.
Does configuring two http elements create two springSecurityFitlerChains? Is UsernamePasswordAuthenticationFilter turned off by default, until I declare form-login? How do I replace SecurityContextPersistenceFilter with a filter which will obtain Authentication from existing JWT-token rather than JSESSIONID?
The Spring security filter chain is a very complex and flexible engine.
Key filters in the chain are (in the order)
- SecurityContextPersistenceFilter (restores Authentication from JSESSIONID)
- UsernamePasswordAuthenticationFilter (performs authentication)
- ExceptionTranslationFilter (catch security exceptions from FilterSecurityInterceptor)
- FilterSecurityInterceptor (may throw authentication and authorization exceptions)
Looking at the current stable release 4.2.1 documentation, section 13.3 Filter Ordering you could see the whole filter chain’s filter organization:
13.3 Filter Ordering
The order that filters are defined in the chain is very important. Irrespective of which filters you are actually using, the order should be as follows:
- ChannelProcessingFilter, because it might need to redirect to a different protocol
- SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and any changes to the SecurityContext can be copied to the HttpSession when the web request ends (ready for use with the next web request)
- ConcurrentSessionFilter, because it uses the SecurityContextHolder functionality and needs to update the SessionRegistry to reflect ongoing requests from the principal
- Authentication processing mechanisms - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter etc - so that the SecurityContextHolder can be modified to contain a valid Authentication request token
- The SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your servlet container
- The JaasApiIntegrationFilter, if a JaasAuthenticationToken is in the SecurityContextHolder this will process the FilterChain as the Subject in the JaasAuthenticationToken
- RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put there
- AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, an anonymous Authentication object will be put there
- ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an appropriate AuthenticationEntryPoint can be launched
- FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied
Now, I’ll try to go on by your questions one by one:
I’m confused how these filters are used. Is it that for the spring provided form-login, UsernamePasswordAuthenticationFilter is only used for /login, and latter filters are not? Does the form-login namespace element auto-configure these filters? Does every request (authenticated or not) reach FilterSecurityInterceptor for non-login url?
Once you are configuring a <security-http> section, for each one you must at least provide one authentication mechanism. This must be one of the filters which match group 4 in the 13.3 Filter Ordering section from the Spring Security documentation I’ve just referenced.
This is the minimum valid security:http element which can be configured:
<security:http authentication-manager-ref="mainAuthenticationManager" entry-point-ref="serviceAccessDeniedHandler"> <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/> </security:http>
Just doing it, these filters are configured in the filter chain proxy:
{ "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter", "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter", "3": "org.springframework.security.web.header.HeaderWriterFilter", "4": "org.springframework.security.web.csrf.CsrfFilter", "5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter", "6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter", "7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter", "8": "org.springframework.security.web.session.SessionManagementFilter", "9": "org.springframework.security.web.access.ExceptionTranslationFilter", "10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor" }
Note: I get them by creating a simple RestController which @Autowires the FilterChainProxy and returns it’s contents:
@Autowired private FilterChainProxy filterChainProxy; @Override @RequestMapping("/filterChain") public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){ return this.getSecurityFilterChainProxy(); } public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){ Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>(); int i = 1; for(SecurityFilterChain secfc : this.filterChainProxy.getFilterChains()){ //filters.put(i++, secfc.getClass().getName()); Map<Integer, String> filters = new HashMap<Integer, String>(); int j = 1; for(Filter filter : secfc.getFilters()){ filters.put(j++, filter.getClass().getName()); } filterChains.put(i++, filters); } return filterChains; }
Here we could see that just by declaring the <security:http> element with one minimum configuration, all the default filters are included, but none of them is of a Authentication type (4th group in 13.3 Filter Ordering section). So it actually means that just by declaring the security:http element, the SecurityContextPersistenceFilter, the ExceptionTranslationFilter and the FilterSecurityInterceptor are auto-configured.
In fact, one authentication processing mechanism should be configured, and even security namespace beans processing claims for that, throwing an error during startup, but it can be bypassed adding an entry-point-ref attribute in <http:security>
If I add a basic <form-login> to the configuration, this way:
<security:http authentication-manager-ref="mainAuthenticationManager"> <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/> <security:form-login /> </security:http>
Now, the filterChain will be like this:
{ "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter", "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter", "3": "org.springframework.security.web.header.HeaderWriterFilter", "4": "org.springframework.security.web.csrf.CsrfFilter", "5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter", "6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter", "7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter", "8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter", "9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter", "10": "org.springframework.security.web.session.SessionManagementFilter", "11": "org.springframework.security.web.access.ExceptionTranslationFilter", "12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor" }
Now, this two filters org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter and org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter are created and configured in the FilterChainProxy.
So, now, the questions:
Is it that for the spring provided form-login, UsernamePasswordAuthenticationFilter is only used for /login, and latter filters are not?
Yes, it is used to try to complete a login processing mechanism in case the request matches the UsernamePasswordAuthenticationFilter url. This url can be configured or even changed it’s behaviour to match every request.
You could too have more than one Authentication processing mechanisms configured in the same FilterchainProxy (such as HttpBasic, CAS, etc).
Does the form-login namespace element auto-configure these filters?
No, the form-login element configures the UsernamePasswordAUthenticationFilter, and in case you don’t provide a login-page url, it also configures the org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, which ends in a simple autogenerated login page.
The other filters are auto-configured by default just by creating a <security:http> element with no security:"none" attribute.
Does every request (authenticated or not) reach FilterSecurityInterceptor for non-login url?
Every request should reach it, as it is the element which takes care of whether the request has the rights to reach the requested url. But some of the filters processed before might stop the filter chain processing just not calling FilterChain.doFilter(request, response);. For example, a CSRF filter might stop the filter chain processing if the request has not the csrf parameter.
What if I want to secure my REST API with JWT-token, which is retrieved from login? I must configure two namespace configuration http tags, rights? Other one for /login with
UsernamePasswordAuthenticationFilter, and another one for REST url’s, with customJwtAuthenticationFilter.
No, you are not forced to do this way. You could declare both UsernamePasswordAuthenticationFilter and the JwtAuthenticationFilter in the same http element, but it depends on the concrete behaviour of each of this filters. Both approaches are possible, and which one to choose finnally depends on own preferences.
Does configuring two http elements create two springSecurityFitlerChains?
Yes, that’s true
Is UsernamePasswordAuthenticationFilter turned off by default, until I declare form-login?
Yes, you could see it in the filters raised in each one of the configs I posted
How do I replace SecurityContextPersistenceFilter with one, which will obtain Authentication from existing JWT-token rather than JSESSIONID?
You could avoid SecurityContextPersistenceFilter, just configuring session strategy in <http:element>. Just configure like this:
<security:http create-session="stateless" >
Or, In this case you could overwrite it with another filter, this way inside the <security:http> element:
<security:http ...> <security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/> </security:http> <beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
EDIT:
One question about “You could too have more than one Authentication processing mechanisms configured in the same FilterchainProxy”. Will the latter overwrite the authentication performed by first one, if declaring multiple (Spring implementation) authentication filters? How this relates to having multiple authentication providers?
This finally depends on the implementation of each filter itself, but it’s true the fact that the latter authentication filters at least are able to overwrite any prior authentication eventually made by preceding filters.
But this won’t necesarily happen. I have some production cases in secured REST services where I use a kind of authorization token which can be provided both as a Http header or inside the request body. So I configure two filters which recover that token, in one case from the Http Header and the other from the request body of the own rest request. It’s true the fact that if one http request provides that authentication token both as Http header and inside the request body, both filters will try to execute the authentication mechanism delegating it to the manager, but it could be easily avoided simply checking if the request is already authenticated just at the begining of the doFilter() method of each filter.
Having more than one authentication filter is related to having more than one authentication providers, but don’t force it. In the case I exposed before, I have two authentication filter but I only have one authentication provider, as both of the filters create the same type of Authentication object so in both cases the authentication manager delegates it to the same provider.
And opposite to this, I too have a scenario where I publish just one UsernamePasswordAuthenticationFilter but the user credentials both can be contained in DB or LDAP, so I have two UsernamePasswordAuthenticationToken supporting providers, and the AuthenticationManager delegates any authentication attempt from the filter to the providers secuentially to validate the credentials.
So, I think it’s clear that neither the amount of authentication filters determine the amount of authentication providers nor the amount of provider determine the amount of filters.
Also, documentation states SecurityContextPersistenceFilter is responsible of cleaning the SecurityContext, which is important due thread pooling. If I omit it or provide custom implementation, I have to implement the cleaning manually, right? Are there more similar gotcha’s when customizing the chain?
I did not look carefully into this filter before, but after your last question I’ve been checking it’s implementation, and as usually in Spring, nearly everything could be configured, extended or overwrited.
The SecurityContextPersistenceFilter delegates in a SecurityContextRepository implementation the search for the SecurityContext. By default, a HttpSessionSecurityContextRepository is used, but this could be changed using one of the constructors of the filter. So it may be better to write an SecurityContextRepository which fits your needs and just configure it in the SecurityContextPersistenceFilter, trusting in it’s proved behaviour rather than start making all from scratch.