Describe the bug OS: macOS Big Sur 11.6 JDK: corretto-1.8 Spring boot: 2.6.1

Consider an API with a single mapping POST /foo and the following 4 test cases: * POST /foo * POST /foo/ * GET /foo * GET /foo/

Controller:

@RestController
public class MainController {

    @PostMapping(path = "/foo", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
    private String foo() {
        return "this is foo";
    }
}

Without spring security (or with a configurer that permits all requests) the result will be the following: * POST /foo -> 200 OK * POST /foo/ -> 200 OK * GET /foo -> 405 Method Not Allowed * GET /foo/ -> 405 Method Not Allowed

Spring security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable()
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }
}

If the configuration is changed in the way to permit only /foo mvc matcher, then the test result will change in the following way: * POST /foo -> 200 OK * POST /foo/ -> 200 OK * GET /foo -> 405 Method Not Allowed * GET /foo/ -> 403 Forbidden

Spring security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable()
                .authorizeRequests()
                .mvcMatchers("/foo").permitAll()
                .anyRequest()
                .authenticated();
    }
}

For some reason while POST /foo/ succeeds, GET /foo/ fails the authentication.

To Reproduce Apply permitAll() only to /foo path using mvc matcher.

Expected behavior GET /foo/ to work the same way as GET /foo, because these requests are the same from spring mvc's perspective.

Sample Sample project to reproduce

Comment From: eleftherias

Thanks for the detailed issue @bigboynaruto.

The behavior you described is how the MvcRequestMatcher is intended to work. When making the requests POST /foo or POST /foo/, the MvcRequestMatcher will search for a matching HandlerMapping and find the POST mapping defined in the Controller, therefore returning a match. When making the requests GET /foo or GET /foo/, there is no matching HandlerMapping found, because the Controller only has a POST mapping. In this case the MvcRequestMatcher will fall back to an AntPathMatcher with the same path pattern, in this case /foo. When using an AntPathMatcher, the request /foo/ will not match the pattern /foo, which is why it returns a 403.

I think the documentation can be improved to describe this case.

Does that make sense?

Comment From: bigboynaruto

Thank you for the explanation! Now this totally makes sense.

Comment From: eleftherias

I noticed that this case is already mentioned in the mvcMatchers Javadocs https://github.com/spring-projects/spring-security/blob/525f40490ceb4e268b8a3b929e7a059c5ae8ee8b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java#L129-L130 Please reopen the issue if you believe additional changes are needed.