Summary

After adding custom JWT filter for authentication, all paths with permitAll() are still passing through the custom JWT filter. So, for example if I have these URI's

/api/users/{id}/topic/{name} (This has to be available for all users. permitAll()) and /api/users/** (other multiple ant matchers URI which are authenticated())

So, If I have a custom JWT filter with pattern matching URI as (/api/users/**) then why is this filter enabled for permitAll (/api/users/{id}/topic/{name} URI)? It doesn't have jwt token.

Is this a bug?

Do I have to keep authenticated URI's with /a/api/user/ and use the pattern for the filter? and /api/users/ with permitAll()? Looks like a bad design to me.

Actual Behavior

permitAll() URI's are passing through custom filter.

Expected Behavior

permitAll() URI's shouldn't pass through custom filter.

Configuration

Version

Latest version

Sample

Comment From: mmouterde

@redbrickss I had a similar issue with :

String authorization = servletRequest.getHeader("Authorization");
        if (authorization != null) {
            SecurityContextHolder.getContext().setAuthentication(new JWTAuthentication(authorization.replaceAll("Bearer ", "")));
        } /* no else case */

I fixed it with :

String authorization = servletRequest.getHeader("Authorization");
        if (authorization != null) {
            SecurityContextHolder.getContext().setAuthentication(new JWTAuthentication(authorization.replaceAll("Bearer ", "")));
        } else {
            SecurityContextHolder.getContext().setAuthentication(null);
        }

Not sure why the context is not refreshed between requests.

Comment From: siddhiparkar151992

I am facing the same issue :| tried all possible combinations but it seems like after custom filter it doesnt work at all.

Comment From: siddhiparkar151992

Update:

Solved it using following config added restricted path to filter others will be permitted

http.csrf()
                .disable().antMatcher("<secure patth>/**").
                authorizeRequests().
                anyRequest().
                authenticated().and()
                .addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(authProvider)

Comment From: rwinch

@siddhiparkar151992 I'm glad you were able to solve this. As an FYI, there is no need for .antMatchers("/**") as this is the default. So you can replace what you have with the following:

http
   .csrf().disable()
   .authorizeRequests()
      .anyRequest().authenticated()
      .and()
   .addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
   .authenticationProvider(authProvider)

PS: You can use fenced code blocks for formatting code. For example:

```java
System.out.println("hi");
System.out.println("there");
```

will render as

System.out.println("hi");
System.out.println("there");

Comment From: rwinch

To anyone still having an issue, it is difficult to reproduce the issue if you have custom code (i.e. a custom authentication filter) without posting it. Please provide a complete sample to reproduce the issue.

Comment From: KevinWingi

Another way to solve the problem is ignoring the URL and if possible with the respective HTTP method overriding the method void configure (WebSecurity web) {} as bellow @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers(HttpMethod.POST, "/agents/login"); }

Comment From: davi004

I am facing similar issues where i have custom filter for JWT validation and also have configured the web.ignoring in place but still the request goes into filter

http.csrf().disable().authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll().and().authorizeRequests().anyRequest().authenticated().and().requestMatchers().antMatchers(gateConfig.getResource()).and().addFilterAfter(new JWTRequestFilter(), UsernamePasswordAuthenticationFilter.class);

Comment From: Kevin2iBi

@davi004 i think you need to remove the authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll() code and just let the web.ignoring configuration for you AUTH_WHITELIST.

Another think you need to add your JWTRequestFiler filter before the UsernamePasswordAuthenticationFilterand not after like thisaddFilterBefore(new JWTRequestFilter(), UsernamePasswordAuthenticationFilter.class)

Comment From: jzheaux

permitAll() has no effect on authentication filters. Spring Security processes authentication first and then authorization, and permitAll() is an authorization matter.

Things essentially happen in this order:

  1. Write Secure Headers, like X-XSS-Protection
  2. Create an Authentication statement (that's what the authentication filters are for)
  3. Decide if that Authentication is enough to allow the request (permitAll() always says "yes")

Step 2 is where a JWT filter would go since that's how you'd figure out what privileges (scopes for example) that token has. That question has to be asked before an authorization decision (permitAll()) can be made.

Note that just because a request is public, that doesn't mean the response won't be rendered differently if there is a user in context. Thus, the separation of these two concerns is important.

It's already been stated that WebSecurity is how to completely ignore certain endpoints. Note, though, that this means that Spring Security won't secure that request in any way (no Step 1, 2, or 3).

Comment From: carlosmontoyargz

Update:

Solved it using following config added restricted path to filter others will be permitted

java http.csrf() .disable().antMatcher("<secure patth>/**"). authorizeRequests(). anyRequest(). authenticated().and() .addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .authenticationProvider(authProvider)

Wow that worked like magic, thank you!

Comment From: sangonzal

@jzheaux Thanks for explaining, your comment was helpful in understanding the steps involved.

I have not been able to find an example for what a WebSecurityConfigurerAdapter would look like in the case where: 1) I have a custom filter (for example a JWT filter) 2) Would like to exclude a URL for steps 2 and 3 in your comment, but not step 1

In other words, I have a URL where I'd like to avoid having authentication or authorization checks be performed (including not running my custom filter, which is an authentication filter), but I'd like to have the Step 1 checks (secure headers, etc) and therefore I can't just use WebSecurity to ignore the URL.

Is there any documentation or examples that you know off?

Comment From: jzheaux

Hi, @sangonzal. Usually, the best way is to ensure that your custom filter simply skips any request it doesn't care about. This is what several of the Spring Security filters do, like LogoutFilter.

You can achieve that using the RequestMatcher API. Your custom filter would do something like:

public class MyCustomFilter extends OncePerRequestFilter {
    private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/relevant/paths/**");

    // ...
    public void doFilterInternal(...) {
        if (this.requestMatcher.matches(request)) {
            // ... this filter should run
        }
        chain.doFilter(request, response);
    }
}

There are several implementations of RequestMatcher that can help your code describe what requests your custom filter should run for. Or, you can implement your own.

However, if you have a use case where you have endpoints that you don't want Spring Security to authenticate or authorize, but still want the security headers, you can register two WebSecurityConfigurerAdapters, one for these special endpoints, and one for the rest:

@Configuration
@Order(1)
public class OnlyHeadersConfig extends WebSecurityConfigurerAdapter {
    protected void configure(HttpSecurity http) {
        http
            .antMatchers("/special/endpoints/**")
                .authorizeRequests((authz) -> authz.anyRequest().permitAll())
                .anonymous();
    }
}

@Configuration
@Order(2)
public class MainSecurityConfig extends WebSecurityConfigurerAdapter {
    protected void configure(HttpSecurity http) {
        http
            .authorizeRequests(authz -> authz.anyRequest().authenticated())
            .addFilterAt(new MyCustomFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

The first config is only applied to the /special/endpoints/** set of requests.

Comment From: sangonzal

@jzheaux Thanks, breaking up the configuration solves the issue.

Follow up question creation of a RequestMatcher for a custom filter. I'm working on a Spring boot starter, and it would be nice if MyCustomFilter was able to tap into RequestMatcher set in HttpSecurity as opposed to having the user have to set it on the custom filter separately. In other words, I'd like Spring Security to apply the same configurations to MyCustomFilter as it does to the other authentication filters, including the URLs it will be matched on.

Do you if there are anyways of having MyCustomFilter inherit the RequestMatcher from HttpSecurity ? Would extending AbstractAuthenticationProcessingFilter instead of OncePerRequestFilter be the right approach?

Comment From: jzheaux

I'm glad that worked for you, @sangonzal.

At this point, I think that we are getting a bit off the topic of the ticket here, which will make it trickier for community members who came for the specific issue found in this ticket.

I think your question is a good one and would be better answered via the StackOverflow community. Please consider posting the question there, and I'm happy to take a look and discuss it further.

Comment From: kiranmadanwad

Update: Solved it using following config added restricted path to filter others will be permitted

java http.csrf() .disable().antMatcher("<secure patth>/**"). authorizeRequests(). anyRequest(). authenticated().and() .addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .authenticationProvider(authProvider) For me it worked only with following code as I just wanted secured API to go through the custom filter and follow the logic there. But thank you for your solution. java http .csrf() .disable().antMatcher("<Secured API>/**"). authorizeRequests() .anyRequest().permitAll() .and() .addFilterAfter(new WellnessAuthFilter(), BasicAuthenticationFilter.class);

Comment From: ArkaBando

java http.csrf() .disable().antMatcher("<secure patth>/**"). authorizeRequests(). anyRequest(). authenticated().and() .addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .authenticationProvider(authProvider)

not solving