spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.45k stars 5.76k forks source link

Unintuitive behavior of multiple servlet contexts and HttpSecurity#securityMatcher pattern #15004

Open arvyy opened 2 weeks ago

arvyy commented 2 weeks ago

Expected Behavior

http.securityMatcher("/actuator/**")

I expected above would match all actuator endpoints and apply the security filter configuration

Current Behavior

If there are more servlet contexts deployed (?) under subpath, security matcher tries to match against relative path from that other servlet context. Specifically, after adding hawtio project, I can see that during request to /actuator/hawtio/keycloak/enabled, the configured matcher is compared to enabled path, and since enabled doesn't match /actuator/**, the current filter chain is skipped and next one is tried.

Context

I've marked this as enhancement instead of a bug, because I presume this could be considered a desired behavior. The solution perhaps could be to explicitly specify this in javadoc of securityMatcher?

A workaround in my case is to use http.securityMatcher(request -> request.getServletPath().startsWith("/actuator"))

sjohnr commented 1 week ago

@arvyy thanks for reaching out!

There's a few things going on in your comment and I feel there might be missing information. I'd like to make sure I (or anyone else reviewing this issue) understands clearly. Can you please provide a minimal, reproducible sample that demonstrates a) the issue you are facing and/or b) what you're trying to do via the workaround? You can provide either steps to reproduce or a MockMvc-style test.

arvyy commented 1 day ago

https://github.com/arvyy/SpringIssue15004

sjohnr commented 1 day ago

Thanks for providing the sample, @arvyy! That is very helpful in clarifying what you're trying to do. In Spring Security 6.x, the matching has indeed changed such that MVC-style matching is automatically applied when Spring MVC is on the classpath. This has the implication that other servlets need to be specifically matched, otherwise the Spring MVC servlet is being matched.

The servlet path for actuator endpoints varies by the endpoint. In your case, it will be /actuator/hawtio/keycloak. I'm not very familiar with hawtio actuator integration, so I don't know why /keycloak is part of the servlet path, but it appears that it is. Therefore, you should use MvcRequestMatcher.Builder (as suggested in the docs, see also gh-13562) like this:

@Bean
@Order(1)
SecurityFilterChain filterChainActuator(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {

    MvcRequestMatcher.Builder mvc =
        new MvcRequestMatcher.Builder(introspector);

    MvcRequestMatcher hawtioMatcher =
        mvc.servletPath("/actuator/hawtio/keycloak")
            .pattern("/**");

    http
        .securityMatcher(hawtioMatcher)
        .authorizeHttpRequests(auth -> auth
            .anyRequest().permitAll()
        );

    return http.build();
}

Note that depending on what else you're trying to do with multiple SecurityFilterChains, you could actually combine them into a single filter chain (which would be preferable in simple cases) like this:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {

    MvcRequestMatcher.Builder mvc =
        new MvcRequestMatcher.Builder(introspector);

    MvcRequestMatcher hawtioMatcher =
        mvc.servletPath("/actuator/hawtio/keycloak")
            .pattern("/**");

    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(hawtioMatcher).permitAll()
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults());

    return http.build();
}

I don't think the startsWith workaround would make a good recommendation here since there is a specific servlet in play that you need to match on and we don't usually want to expose all actuator endpoints by default.

I do feel like the information for how to do this is quite findable, for example starting from Security Matchers under Authorize HttpServletRequests. Perhaps just improving javadoc would be in order. Or do you feel more could be done with reference docs?

Would you be interested in submitting a PR for one or both? I'm happy to work through it with you.