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.