Understanding spring security and filter chain

This is fourth post to our Spring Security series. In this post, we will try to analyze behind the scenes magic that spring does for us. I am hoping this will help clear some of the confusions regarding Spring Security.

First of all, it is very important to debug Spring Security. We can debug spring based application with following property in application.properties

logging.level.*= # Log levels severity mapping. For instance `logging.level.org.springframework=DEBUG

In order to enable debug level log, we need to mention spring security package:

logging.level.org.springframework.security=DEBUG
logging:
  level:
     org:
       springframework:
         security: DEBUG

You need to put either in application.properties or application.yml but not in both files.

Spring Security filter ordering:

Let’s look at Spring Security filter ordering, I borrowed from Spring Doc. 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

If we aren’t concerned about any other things except for filter chain, we can control logging in granular level just logging the FilterChainProxy class.

logging:
  level:
     org:
       springframework:
         security:
          web:
            FilterChainProxy: DEBUG

In addition, we can also use @EnableWebSecurity(debug = true) which adds an additional filter that logs the list of filters that’s going to be applied for the particular request in the console. I find this very useful while debugging and working with Spring Security filters. Here’s an example of the console log:

************************************************************

Request received for GET '/secure/user':

org.apache.catalina.connector.RequestFacade@65329e2e

servletPath:/secure/user
pathInfo:null
headers: 
host: localhost:8080
user-agent: curl/7.47.0
accept: */*


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************

It’s important to note that depending on the configuration we state, the order and number of the filters in the filter chain is going to change. For example, http.anonymous().disable() in WebSecurityConfigurerAdapter would exclude  AnonymousAuthenticationFilter from the filter chain.

Sometimes, it is necessary to add custom filters and the order of the filter really matters in such case. There are 2 ways we can order the custom filter in Spring Security filter chain:

  • Using addFilter method of HttpSecurity class while configuration Spring Security. There are 4 different method variations that is provided in HttpSecurity class:
    • addFilter – Adds a Filter that must be an instance of or extend one of the Filters provided within the Security framework.
    • addFilterAfter – Allows adding a Filter after one of the known Filter classes.
    • addFilterAt – Adds the Filter at the location of the specified Filter class.
    • addFilterBefore – Allows adding a Filter before one of the known Filter classes.

Here’s an snippet from our previous post where we are adding custom filter using addFilterBefore method.

@Override
 protected void configure(HttpSecurity http) throws Exception {
 http.addFilterBefore(getCustomAuthenticationFilter("/auth/login"), UsernamePasswordAuthenticationFilter.class)
.......
.......
protected CustomFormAuthenticationFilter getCustomAuthenticationFilter(String pattern) {
    CustomFormAuthenticationFilter customAuthenticationFilter =
        new CustomFormAuthenticationFilter(new AntPathRequestMatcher(pattern), userDetailsService, jwtSecretKey);
    customAuthenticationFilter.setAuthenticationManager(this.authenticationManager);
    return customAuthenticationFilter;
}
.......
  • Using FilterRegistrationBeanto register the filter and order the filter inside security filter chain in @configuration class.
    @Bean
    FilterRegistrationBean myFilterRegistration () {
          FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
          filterRegistrationBean.setOrder(4);
          filterRegistrationBean.setFilter(new CustomSecureFilter());
          filterRegistrationBean.setUrlPatterns(Arrays.asList("/secure/*"));
          return filterRegistrationBean;
    }

    While doing this we just have to make sure the we keep track of spring security filter chain order so that we can apply our filter before or after security filter chain. security.filter-order=0 is the default property in application.properties. We can change the value of this property such that our custom filter has precedence over spring security filter. In the above example, order is set to 4 which we can take precedence if we set security.filter-order=5.

  • From spring security architecture document, we can also use @Beans of type Filter can have an @Order or implement Ordered to define the order of the filter.

Spring Security Authentication and Authorization:

We’ll look into Spring Security authentication and authorisation process. We’ll discuss some of the important components here:

  • Authentication Filter is responsible to intercept and delegate the authentication process to AuthenticationManagerUsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter all do the same thing however the way they process the authentication flow is different. The underlying idea if to delegate the authentication process to AutheticationManager. If you read previous post with custom filter then you’ll get the idea. During the process of login one of these filter retrieves the user credentials from the http request which is used to form the Authentication object and then delegates to AuthenticationManagerfor authentication.
  •  AuthenticationManager is the main interface behind spring authentication.
    public interface AuthenticationManager {
    
      Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
    }

    The authenticate method does one of three things, 1) authenticates returning a fully populated Authentication object (including granted authorities) if successful. 2) throws AuthenticationException if something goes wrong. Exception must be one of three: DisabledException (for disabled account), LockedException (for locked account), BadCredentialsException (for invalid credentials). 3) returns null if it can’t decide to authenticate.

    The default Spring Security authentication manager is implemented by ProviderManager which implements this interface. This loops through the provided authentication providers to match the authentication provider that can handle the authentication object that is passed down the stream. One of the main job of AuthenticationManager is to delegate authentication flow to AuthenticationProvider when the correct Authentication object is found.

  • AuthenticationProvider actually authenticates the user using UserDetailsService. Of course, we can implement our custom authentication provider the way we want to authenticate our user. Thus, we can have multiple AuthenticationProvider in our application which may follow different way of authentication. One application may have more than one authentication providers. In Spring Security, ProviderManager matches the right AuthenticationProvider as per the provided Authentication object. It uses supports(Class<?> authentication) method of AuthenticationProvider to match if it can apply against the Authentication object. Below is the code snippet from ProviderManager authenticate method which makes it clear:
public Authentication authenticate(Authentication authentication){
....
for (AuthenticationProvider provider : getProviders()) {
      if (!provider.supports(toTest)) {
        continue;
      }

      if (debug) {
        logger.debug("Authentication attempt using "
            + provider.getClass().getName());
      }

      try {
        result = provider.authenticate(authentication);

        if (result != null) {
          copyDetails(authentication, result);
          break;
        }
      }
.....
.....
  • Once the authentication process is done, next part of the process is to determine if the user has access to the requested resource. AccessDecisionManager is the interface that is responsible for doing this task. Authorization process is very similar to the authentication process. AccessDecisionManager is analogous to AuthenticationManager. There are 3 implementations of AccessDecisionManager:AffirmativeBased, ConsensusBased and UnanimousBased all of which extend AbstractAccessDecisionManager which in turn implement AccessDecisionManager. Regards of all three implementation class, the decision making flow is delegated to AccessDecisionVoter which is again very analogous to AuthenticationProvider from our authentication process. The implementation class of AccessDecisionVoter is responsible to make the decision of providing the access or not for the requested resource. Although there are many implementations, there are some implementation that you may be interested to look into:  AuthenticatedVoter  and  RoleVoter.

Must Read:

Leave a Reply

Be the First to Comment!

Notify of
avatar
wpDiscuz