Adapt to change in Security's filtering behavior

Spring Security now filters every dispatch by default and not only
once-per-request. Security configuration has been updated in a number of
places to restore the old behavior as needed for the tests to pass.
gh-31703 has been opened to review this and to investigate if we can
now remove the error page security filter and rely on the filtering of
every dispatch instead.

In addition to switching to once-per-request filtering where needed,
this commit also restructures the configuration of the error page
security filter. The restructuring was necessary to ensure that the
privilege evaluator bean has been defined before the conditions on the
error page security filter are evaluated. Without the change, the filter
was no longer being configured as the privilege evaluator hadn't been
defined before the on bean condition was evaluated. We may want to back
port this change as the ordering doesn't appear to have been defined
before and we were just getting lucky.

See gh-31622
See spring-projects/spring-security#11466
pull/31712/head
Andy Wilkinson 2 years ago
parent ec14630bc4
commit 4bd3534b7d

@ -0,0 +1,51 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.servlet;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
/**
* Configures the {@link ErrorPageSecurityFilter}.
*
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebInvocationPrivilegeEvaluator.class)
@ConditionalOnBean(WebInvocationPrivilegeEvaluator.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
class ErrorPageSecurityFilterConfiguration {
@Bean
FilterRegistrationBean<ErrorPageSecurityFilter> errorPageSecurityFilter(ApplicationContext context) {
FilterRegistrationBean<ErrorPageSecurityFilter> registration = new FilterRegistrationBean<>(
new ErrorPageSecurityFilter(context));
registration.setDispatcherTypes(DispatcherType.ERROR);
return registration;
}
}

@ -40,7 +40,8 @@ import org.springframework.security.authentication.DefaultAuthenticationEventPub
@AutoConfiguration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
@Import({ SpringBootWebSecurityConfiguration.class, ErrorPageSecurityFilterConfiguration.class,
SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean

@ -16,18 +16,12 @@
package org.springframework.boot.autoconfigure.security.servlet;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@ -35,7 +29,6 @@ import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
/**
* {@link Configuration @Configuration} class securing servlet applications.
@ -69,24 +62,6 @@ class SpringBootWebSecurityConfiguration {
}
/**
* Configures the {@link ErrorPageSecurityFilter}.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebInvocationPrivilegeEvaluator.class)
@ConditionalOnBean(WebInvocationPrivilegeEvaluator.class)
static class ErrorPageSecurityFilterConfiguration {
@Bean
FilterRegistrationBean<ErrorPageSecurityFilter> errorPageSecurityFilter(ApplicationContext context) {
FilterRegistrationBean<ErrorPageSecurityFilter> registration = new FilterRegistrationBean<>(
new ErrorPageSecurityFilter(context));
registration.setDispatcherTypes(DispatcherType.ERROR);
return registration;
}
}
/**
* Adds the {@link EnableWebSecurity @EnableWebSecurity} annotation if Spring Security
* is on the classpath. This will make sure that the annotation is present with

@ -62,6 +62,7 @@ public class SecurityConfiguration {
.hasRole("ACTUATOR");
requests.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
requests.antMatchers("/foo").permitAll();
requests.antMatchers("/error").permitAll();
requests.antMatchers("/**").hasRole("USER");
});
http.cors(Customizer.withDefaults());

@ -69,7 +69,8 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests((requests) -> requests.anyRequest().fullyAuthenticated());
http.authorizeRequests((requests) -> requests.anyRequest().fullyAuthenticated()
.filterSecurityInterceptorOncePerRequest(true));
http.formLogin((form) -> form.loginPage("/login").permitAll());
http.exceptionHandling((exceptions) -> exceptions.accessDeniedPage("/access"));
return http.build();
@ -85,7 +86,8 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestMatcher(EndpointRequest.toAnyEndpoint());
http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
http.authorizeRequests(
(requests) -> requests.anyRequest().authenticated().filterSecurityInterceptorOncePerRequest(true));
http.httpBasic();
return http.build();
}

@ -47,6 +47,7 @@ class CustomServletPathErrorPageTests extends AbstractErrorPageTests {
http.authorizeRequests((requests) -> {
requests.antMatchers("/custom/servlet/path/public/**").permitAll();
requests.anyRequest().fullyAuthenticated();
requests.filterSecurityInterceptorOncePerRequest(true);
});
http.httpBasic();
http.formLogin((form) -> form.loginPage("/custom/servlet/path/login").permitAll());

@ -46,6 +46,7 @@ class ErrorPageTests extends AbstractErrorPageTests {
http.authorizeRequests((requests) -> {
requests.antMatchers("/public/**").permitAll();
requests.anyRequest().fullyAuthenticated();
requests.filterSecurityInterceptorOncePerRequest(true);
});
http.httpBasic();
http.formLogin((form) -> form.loginPage("/login").permitAll());

@ -48,6 +48,7 @@ class NoSessionErrorPageTests extends AbstractErrorPageTests {
.authorizeRequests((requests) -> {
requests.antMatchers("/public/**").permitAll();
requests.anyRequest().authenticated();
requests.filterSecurityInterceptorOncePerRequest(true);
});
http.httpBasic();
return http.build();

Loading…
Cancel
Save