Describe the bug I have two endpoints with path variables. One endpoint is specified with two path variables and the other with one path variable. I want to open the endpoint with two path variables via the MvcRequestMatcher. But when I do this, the other endpoint with one path variable is also opened.

In my case I use Spring-Security v5.6.1

RestController:

@RestController
public class TestController
{
    /**
     * Secured endpoint by {@link SecurityConfiguration}
     */
    @GetMapping("{username}/secured")
    public String getSecuredEndpoint(@PathVariable String username)
    {
        return username;
    }

    /**
     * Open endpoint by {@link SecurityConfiguration}
     */
    @GetMapping("{firstname}/{lastname}")
    public String getOpenEndpoints(@PathVariable String firstname, @PathVariable String lastname)
    {
        return firstname + " " + lastname;
    }
}

SecurityConfiguration

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
    @Autowired
    private HandlerMappingIntrospector handlerMappingIntrospector;

    @Override
    public void configure(HttpSecurity http) throws Exception
    {
        var mvcMatcher = new MvcRequestMatcher(handlerMappingIntrospector, "/{firstname}/{lastname}");
        mvcMatcher.setMethod(HttpMethod.GET);

        http.authorizeRequests()
                .requestMatchers(mvcMatcher)
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf()
                .ignoringRequestMatchers(mvcMatcher);
    }
}

If I omit the suffix secured from the secured endpoint, the matching works properly.

It seems as if it cannot correctly assign the endpoint for the MvcRequestMatcher when resolving the path variables.

To Reproduce The problem can be simulated with the code examples given above.

Expected behavior Only the endpoint specified via the MvcRequestMatcher may match.

Sample Repository: https://github.com/npriebe/mvc-path-variable-matching

Comment From: sdaus

Hi, is there any update on this? I'm also experiencing this problem.

Comment From: jzheaux

Thanks for reaching out, @npriebe.

What the above is doing is specifying Spring Security placeholders in order to articulate authorization rules based on the first and second element of the path. Security sees the /{firstname}/{lastname} rule precisely the same as:

.requestMatchers("/{path}/{subpath}")

or

.requestMatchers("/*/*")

In other words, the names here have nothing to do with the names that Spring MVC uses. Instead, they allow you to name the request matcher parts so that you can use them in authorization rules like so:

.requestMatchers("/{firstname}/{lastname}").access(allOf(
    hasAuthority("USER"), 
    (auth, ctx) -> users.isAllowed(ctx.getVariables().get("lastname"))
))

The purpose of MvcRequestMatcher is not to match a Security pattern to a specific controller method (that's what method security is for). Instead, it's to delegate the path matching to MVC, should the HttpServletRequest be one that MVC recognizes.

To express this correctly in Spring Security, first specify a rule for the more specific endpoint like so:

.requestMatchers("/{username}/secured").authenticated()
.requestMatchers("/{firstname}/{lastname}").permitAll()
.anyRequest().authenticated()

This becomes clearer when you replace the templates with the * that they replace:

.requestMatchers("/*/secured").authenticated()
.requestMatchers("/*/*").permitAll()
.anyRequest().authenticated()

If it's important to have Spring Security request matching target a given controller method, you can create a custom RequestMatcher that coordinates with RequestMappingHandlerMapping to find it.

Let me know if this doesn't seem to address your concern, and I'm happy to chat further.