-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fail when any filter chain declared after an AnyRequestMatcher filter chain #15220
Comments
Hi @jzheaux. This will only work with |
Good question, @CrazyParanoid. Yes, it may be a surprise, though it's an important one since their configuration is broken. When an any-request filter chain comes before another filter chain, that second filter chain is never consulted, perhaps unbeknownst to the developer. Updating to the next minor release will help them fix this configuration bug. |
Storing the request matcher outside of the for loop means that if one of the SecurityFilterChain instances is not of type DefaultSecurityFilterChain, then the error may print out an earlier request matcher instead of the current one. Instead, this commit changes to print out the entire filter chain so that it can be inside of the for loop, regardless of type. Issue gh-15220
@jzheaux Appreciate this write up! Solved an issue we were having recently |
Hi, I just stumbled across this change when upgrading to Spring Boot 3.4 but in our case the overwriting was intended. In production we use a filter chain which allows unauthenticated access only to some management endpoints. Any other request requires authentication. But in some tests/example applications we "overwrite" this "management-only" filter chain by a filter chain which allows request to all endpoints: @Order(0)
@Bean
SecurityFilterChain managementOnlySecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/actuator/**", "/startup-report/**")
.authorizeHttpRequests(auth -> {
auth.requestMatchers(mvc.pattern(HEALTH_ENDPOINT)).permitAll();
// ...
auth.anyRequest().authenticated();
}); @Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain permitAllSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> {
auth.requestMatchers("/*/authenticationRequired").authenticated();
auth.anyRequest().permitAll();
});
} Now this approach doesn't work anymore. Is there any other way how to achieve this overwriting? I mean I could make the production filter chain somehow configurable to disable it in other places by configuration. Just wondering if I do something wrong here and there is a better solution?! |
|
You mean I block out the first filter, i.e. managementOnlySecurityFilterChain because The managementOnlySecurityFilterChain is always created in our production auto configuration. And before I could overwrite it from my actuall application by defining the higher precedence filter chain. Just wanted to confirm that I didn't misunderstood the new behavior (I understand the motivation behind it) and that there is no way to avoid it. Basically it's not possible anymore to have two filter chains which match any request. |
To do what you want to do, either suppress the @Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain permitAllSecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/**") // Matches all, but is not instanceof AnyRequestMatcher
http.authorizeHttpRequests(auth -> {
auth.requestMatchers("/*/authenticationRequired").authenticated();
auth.anyRequest().permitAll();
});
} |
Thanks to @jzheaux for fixing this developer quality-of-life issue. As an added bonus for anyone using Remember to read the docs, and consider |
@jespersm Thanks for the quick help. The "cheat" is what I was looking for 😀 I also read the docs now 😉 and just in case someone is looking for a way to combine multiple antMatchers, this seems to work: http.securityMatchers(c -> c.requestMatchers(antMatcher("/actuator/**"), antMatcher("/startup-report/**"))) |
I have an external library that implements basic and oauth2 authorization. And I have a @Bean
@ConditionalOnMissingBean
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer(
@Qualifier("basicAndFormMvc") MvcRequestMatcher.Builder mvc) {
return customizer ->
customizer.requestMatchers(mvc.pattern("/api/**")).authenticated().anyRequest().permitAll();
}
@Order(3)
@Bean("basicAndFormAuthFilter")
public SecurityFilterChain filterChain(
HttpSecurity http, AuthorizeRequestsCustomizer authorizeRequestsCustomizer) throws Exception {
http.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.csrf(AbstractHttpConfigurer::disable)
.cors(withDefaults())
.httpBasic(withDefaults())
.formLogin(withDefaults())
.exceptionHandling(
c ->
c.defaultAuthenticationEntryPointFor(
customAuthenticationEntryPoint(), new AntPathRequestMatcher("/**")))
.authorizeHttpRequests(authorizeRequestsCustomizer);
return http.build();
} But when starting, the error described above occurs:
It comes from an existing @Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class OAuth2SecurityFilterChainConfiguration {
@Bean
@ConditionalOnBean(JwtDecoder.class)
SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults()));
return http.build();
}
} Since the following How can I get around this error? |
If you want to disable the starter's config, you could use the corresponding Spring mechanisms for that, as discussed in https://stackoverflow.com/questions/67020215/how-to-turn-off-spring-security-auto-configuration That's a better approach than the cheat I described above. |
Thanks @jespersm. I was thinking about this option, but stopped because of:
I think I will resume work in this direction. |
By default, a filter chain's
securityMatcher
defaults to any request:This means that configurations that use multiple default filter chains can get unexpected results. Since technically both chains match any request, the first chain is always picked and the second one is never picked.
Spring Security should help by failing during startup when an application has any filter chain that is defined after an "any request" filter chain. For example, a configuration like this is problematic:
since the first filter chain will always consume any request and the second filter chain will never get exercised.
For clarity, an example fix in the above case could be:
so that neither of them captures every request.
Or a more preferred one would be:
as this one still has an "any request" filter chain that acts as a catch-all and is correctly placed as the last filter chain.
I think this check could be done in
WebSecurity#performBuild
after all theSecurityFilterChain
instances are fully built. Something like this might do the trick:The text was updated successfully, but these errors were encountered: