Bläddra i källkod

Exclude SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor from AOT processing

Closes gh-14362
Marcus Hert Da Coregio 1 år sedan
förälder
incheckning
8a93178da7

+ 64 - 39
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java

@@ -29,12 +29,14 @@ import jakarta.servlet.http.HttpServletRequest;
 import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
 import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
 import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.beans.factory.support.RegisteredBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.annotation.Bean;
@@ -110,56 +112,79 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
 		}
 	}
 
+	@Bean
+	static SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor springSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor() {
+		return new SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor();
+	}
+
 	/**
-	 * Used to ensure Spring MVC request matching is cached.
-	 *
-	 * Creates a {@link BeanDefinitionRegistryPostProcessor} that detects if a bean named
+	 * Used to ensure Spring MVC request matching is cached. Creates a
+	 * {@link BeanDefinitionRegistryPostProcessor} that detects if a bean named
 	 * HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME is defined. If so, it moves the
 	 * AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME to another bean name
 	 * and then adds a {@link CompositeFilter} that contains
 	 * {@link HandlerMappingIntrospector#createCacheFilter()} and the original
 	 * FilterChainProxy under the original Bean name.
+	 *
 	 * @return
 	 */
-	@Bean
-	static BeanDefinitionRegistryPostProcessor springSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor() {
-		return new BeanDefinitionRegistryPostProcessor() {
-			@Override
-			public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+	static class SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor
+			implements BeanDefinitionRegistryPostProcessor {
+
+		@Override
+		public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
+			if (!registry.containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
+				return;
 			}
 
-			@Override
-			public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
-				if (!registry.containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
-					return;
-				}
+			BeanDefinition hmiRequestTransformer = BeanDefinitionBuilder
+				.rootBeanDefinition(HandlerMappingIntrospectorRequestTransformer.class)
+				.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)
+				.getBeanDefinition();
+			registry.registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME + "RequestTransformer",
+					hmiRequestTransformer);
+
+			BeanDefinition filterChainProxy = registry
+				.getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
+
+			BeanDefinitionBuilder hmiCacheFilterBldr = BeanDefinitionBuilder
+				.rootBeanDefinition(HandlerMappingIntrospectorCachFilterFactoryBean.class)
+				.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+			ManagedList<BeanMetadataElement> filters = new ManagedList<>();
+			filters.add(hmiCacheFilterBldr.getBeanDefinition());
+			filters.add(filterChainProxy);
+			BeanDefinitionBuilder compositeSpringSecurityFilterChainBldr = BeanDefinitionBuilder
+				.rootBeanDefinition(CompositeFilterChainProxy.class)
+				.addConstructorArgValue(filters);
+
+			registry.removeBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
+			registry.registerBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
+					compositeSpringSecurityFilterChainBldr.getBeanDefinition());
+		}
+
+		@Override
+		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+
+		}
+
+	}
+
+	/**
+	 * Used to exclude the
+	 * {@link SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor}
+	 * from AOT processing. See <a href=
+	 * "https://github.com/spring-projects/spring-security/issues/14362">gh-14362</a>
+	 */
+	static class SpringSecurityHandlerMappingIntrospectorBeanRegistrationExcludeFilter
+			implements BeanRegistrationExcludeFilter {
+
+		@Override
+		public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
+			Class<?> beanClass = registeredBean.getBeanClass();
+			return SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor.class == beanClass;
+		}
 
-				BeanDefinition hmiRequestTransformer = BeanDefinitionBuilder
-					.rootBeanDefinition(HandlerMappingIntrospectorRequestTransformer.class)
-					.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)
-					.getBeanDefinition();
-				registry.registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME + "RequestTransformer",
-						hmiRequestTransformer);
-
-				BeanDefinition filterChainProxy = registry
-					.getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
-
-				BeanDefinitionBuilder hmiCacheFilterBldr = BeanDefinitionBuilder
-					.rootBeanDefinition(HandlerMappingIntrospectorCachFilterFactoryBean.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
-
-				ManagedList<BeanMetadataElement> filters = new ManagedList<>();
-				filters.add(hmiCacheFilterBldr.getBeanDefinition());
-				filters.add(filterChainProxy);
-				BeanDefinitionBuilder compositeSpringSecurityFilterChainBldr = BeanDefinitionBuilder
-					.rootBeanDefinition(CompositeFilterChainProxy.class)
-					.addConstructorArgValue(filters);
-
-				registry.removeBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
-				registry.registerBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
-						compositeSpringSecurityFilterChainBldr.getBeanDefinition());
-			}
-		};
 	}
 
 	/**

+ 3 - 0
config/src/main/resources/META-INF/spring/aot.factories

@@ -3,3 +3,6 @@ org.springframework.security.config.annotation.authentication.configuration.Auth
 
 org.springframework.aot.hint.RuntimeHintsRegistrar=\
 org.springframework.security.config.aot.hint.OAuth2LoginRuntimeHints
+
+org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
+org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration.SpringSecurityHandlerMappingIntrospectorBeanRegistrationExcludeFilter

+ 53 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configuration/SpringSecurityHandlerMappingIntrospectorBeanRegistrationExcludeFilterTests.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2023 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.security.config.annotation.web.configuration;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
+import org.springframework.beans.factory.support.RegisteredBean;
+import org.springframework.core.io.support.SpringFactoriesLoader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for
+ * {@link WebMvcSecurityConfiguration.SpringSecurityHandlerMappingIntrospectorBeanRegistrationExcludeFilter}
+ *
+ * @author Marcus da Coregio
+ */
+class SpringSecurityHandlerMappingIntrospectorBeanRegistrationExcludeFilterTests {
+
+	@Test
+	void springSecurityHandlerMappingIntrospectorBeanRegistrationExcludeFilterShouldBeExcludedFromAotProcessing()
+			throws ClassNotFoundException {
+		List<BeanRegistrationExcludeFilter> filters = SpringFactoriesLoader
+			.forResourceLocation("META-INF/spring/aot.factories")
+			.load(BeanRegistrationExcludeFilter.class);
+		RegisteredBean registeredBean = mock(RegisteredBean.class);
+		Class<?> clazz = Class.forName(
+				"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor");
+		given(registeredBean.getBeanClass()).willReturn((Class) clazz);
+		boolean isExcluded = filters.stream().anyMatch((f) -> f.isExcludedFromAotProcessing(registeredBean));
+		assertThat(isExcluded).isTrue();
+	}
+
+}