[Solved] Spring Boot – permitAll not working using SecurityFilterChain – Spring

by
Ali Hasan
cucumber-java spring spring-boot spring-mvc spring-security

Quick Fix: To resolve the issue, you can override the shouldNotFilter() method in your custom OncePerRequestFilter implementation and specify the paths that should be exempted from authentication.

The Problem:

In a Spring Boot application, permitAll() is not working as expected. The CustomOncePerRequestFilter is being called for endpoints that should be accessible to all users, even though these endpoints are specified in the permitAll() configuration. The goal is to make certain endpoints accessible without authentication, but this is not happening as intended.

The Solutions:

Solution 1: Override shouldNotFilter() method

To resolve the issue where `permitAll()` is not working as intended, it is recommended to override the `shouldNotFilter()` method within the custom `OncePerRequestFilter` implementation. This method determines which requests should be exempted from the filter and thus not require authentication. In this case, the `shouldNotFilter()` method should include the specific paths that need to be accessible to all users without authentication, such as:

“`java
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return request.getServletPath().startsWith(“/art”);
}
“`

Solution 2: Ignore Empty Authorization Header

The solution involves ignoring an empty Authorization header in the CustomOncePerRequestFilter while allowing the SecurityFilterChain to handle the request matchers correctly.

Here’s an example of how to implement this in Java:

@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    Optional<String> token = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
            .map(authHeader -> authHeader.substring(Constants.BEARER_HEADER.length()));

    if (token.isEmpty()) {
        filterChain.doFilter(request, response);
        return;
    }

    // Continue with authentication and filtering logic...

}

With this modification, the CustomOncePerRequestFilter will be effectively ignored when the Authorization header is empty, allowing the SecurityFilterChain to handle the request matchers as intended.

Solution 3: Clarifying Filter Chain Operation

In Spring Security, filters are organized into a chain. Each filter processes a request in order, and if a filter does not short-circuit the chain, subsequent filters will continue to process the request.

permitAll() does not short-circuit the filter chain. It merely adds an entry to the authorization manager. The AuthorizationFilter (formerly FilterSecurityInterceptor), which is one of the last filters called, uses the authorization manager to determine if the request should be authorized or rejected.

In your CustomOncePerRequestFilter, if you cannot authenticate the user, you should pass the request down the filter chain, allowing subsequent filters to process it.

Solution 4: Multiple Security Filter Chains

In this approach, multiple security filter chains are created to handle different security levels. The first chain is responsible for unsecured URLs, while the second handles secured ones.

@Bean
@Order(2)
protected SecurityFilterChain filterChainPublic(HttpSecurity http) throws Exception {
    return http.csrf().disable()
        .authorizeHttpRequests()
        .antMatchers("/art/**")
        .permitAll()
        .and()
        .authorizeHttpRequests()
        .antMatchers("/**")
        .authenticated()
        .and()
        .cors()
        .disable()
        .build();
}

@Bean
@Order(1)
protected SecurityFilterChain filterChainPrivate(HttpSecurity http) throws Exception {
    return http.csrf().disable()
        .requestMatchers()
        .antMatchers("/api/**") // only apply the security configuration to requests that start with /api
        .and()
        .authorizeHttpRequests()
        .antMatchers("/api/contenteditor/feed/**").hasAnyRole("SITE_ADMIN", "STANDARD_ADMIN", "DOMAIN_MEMBER")
        .and()
        .addFilterBefore(new CustomOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class)
        .cors()
        .disable()
        .build();
}

**How it works:**

  • The first filter chain (filterChainPublic) defines a more open security configuration, allowing access to all URLs without authentication.
  • The second filter chain (filterChainPrivate) applies more restrictive security measures to specific URLs, such as those starting with “/api/**”.
  • The order of the beans is crucial. Spring will use the first chain that conforms to the URL. Hence, the most specific chain (filterChainPrivate) should be placed first, followed by the more general chain (filterChainPublic).

This approach ensures that unsecured URLs are accessible without authentication, while secured URLs require the appropriate credentials.

Q&A

Why is permitAll not working using SecurityFilterChain in Spring Boot?

AuthorizeHttpRequests() allows multiple invocations resulting in additive behavior rather than a short-circuiting behavior.

How should I fix this permitAll issue with SecurityFilterChain in Spring Boot?

Override the shouldNotFilter() method within a Custom OncePerRequestFilter implementation to include all the paths that require exemption from authentication.

How to create two filter chains based on security levels in Spring Boot?

Use multiple SecurityFilterChain beans with different requestMatchers to define the scope of each filter chain, and order them appropriately.

Video Explanation:

The following video, titled "Spring Security without the WebSecurityConfigurerAdapter - YouTube", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

If you are using Spring Boot 2.7.x or above you will need to understand how to use Spring Security without the WebSecurityConfigurerAdapter ...