|
@@ -16,16 +16,29 @@
|
|
|
|
|
|
package org.springframework.security.config.annotation.web.configuration;
|
|
|
|
|
|
+import java.io.IOException;
|
|
|
import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
import jakarta.servlet.Filter;
|
|
|
+import jakarta.servlet.FilterChain;
|
|
|
+import jakarta.servlet.ServletException;
|
|
|
+import jakarta.servlet.ServletRequest;
|
|
|
+import jakarta.servlet.ServletResponse;
|
|
|
+import jakarta.servlet.http.HttpServletRequest;
|
|
|
|
|
|
+import org.springframework.beans.BeanMetadataElement;
|
|
|
+import org.springframework.beans.BeansException;
|
|
|
import org.springframework.beans.factory.ObjectProvider;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.config.BeanDefinition;
|
|
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
|
|
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.context.annotation.Bean;
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
import org.springframework.context.annotation.DependsOn;
|
|
@@ -45,11 +58,18 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
|
|
import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor;
|
|
|
import org.springframework.security.context.DelegatingApplicationListener;
|
|
|
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
|
|
import org.springframework.security.web.FilterChainProxy;
|
|
|
import org.springframework.security.web.FilterInvocation;
|
|
|
import org.springframework.security.web.SecurityFilterChain;
|
|
|
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
|
|
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
|
|
|
+import org.springframework.security.web.debug.DebugFilter;
|
|
|
+import org.springframework.security.web.firewall.HttpFirewall;
|
|
|
+import org.springframework.security.web.firewall.RequestRejectedHandler;
|
|
|
+import org.springframework.web.filter.CompositeFilter;
|
|
|
+import org.springframework.web.filter.ServletRequestPathFilter;
|
|
|
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
|
|
|
|
|
/**
|
|
|
* Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
|
|
@@ -186,6 +206,56 @@ public class WebSecurityConfiguration implements ImportAware {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 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 springSecurityPathPatternParserBeanDefinitionRegistryPostProcessor() {
|
|
|
+ return new BeanDefinitionRegistryPostProcessor() {
|
|
|
+ @Override
|
|
|
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
|
|
+ BeanDefinition filterChainProxy = registry
|
|
|
+ .getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
|
|
|
+
|
|
|
+ if (filterChainProxy.getResolvableType().isAssignableFrom(CompositeFilterChainProxy.class)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ BeanDefinitionBuilder pppCacheFilterBldr = BeanDefinitionBuilder
|
|
|
+ .rootBeanDefinition(ServletRequestPathFilter.class)
|
|
|
+ .setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
|
|
+
|
|
|
+ ManagedList<BeanMetadataElement> filters = new ManagedList<>();
|
|
|
+ filters.add(pppCacheFilterBldr.getBeanDefinition());
|
|
|
+ filters.add(filterChainProxy);
|
|
|
+ BeanDefinitionBuilder compositeSpringSecurityFilterChainBldr = BeanDefinitionBuilder
|
|
|
+ .rootBeanDefinition(CompositeFilterChainProxy.class)
|
|
|
+ .addConstructorArgValue(filters);
|
|
|
+
|
|
|
+ if (filterChainProxy.getResolvableType().isInstance(DebugFilter.class)) {
|
|
|
+ compositeSpringSecurityFilterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(DebugFilter.class)
|
|
|
+ .addConstructorArgValue(compositeSpringSecurityFilterChainBldr.getBeanDefinition());
|
|
|
+ }
|
|
|
+
|
|
|
+ registry.removeBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
|
|
|
+ registry.registerBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
|
|
|
+ compositeSpringSecurityFilterChainBldr.getBeanDefinition());
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* A custom version of the Spring provided AnnotationAwareOrderComparator that uses
|
|
|
* {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class
|
|
@@ -219,4 +289,126 @@ public class WebSecurityConfiguration implements ImportAware {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Extends {@link FilterChainProxy} to provide as much passivity as possible but
|
|
|
+ * delegates to {@link CompositeFilter} for
|
|
|
+ * {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}.
|
|
|
+ */
|
|
|
+ static class CompositeFilterChainProxy extends FilterChainProxy {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Used for {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}
|
|
|
+ */
|
|
|
+ private final Filter doFilterDelegate;
|
|
|
+
|
|
|
+ private final FilterChainProxy springSecurityFilterChain;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates a new instance
|
|
|
+ * @param filters the Filters to delegate to. One of which must be
|
|
|
+ * FilterChainProxy.
|
|
|
+ */
|
|
|
+ CompositeFilterChainProxy(List<? extends Filter> filters) {
|
|
|
+ this.doFilterDelegate = createDoFilterDelegate(filters);
|
|
|
+ this.springSecurityFilterChain = findFilterChainProxy(filters);
|
|
|
+ }
|
|
|
+
|
|
|
+ CompositeFilterChainProxy(Filter delegate, FilterChainProxy filterChain) {
|
|
|
+ this.doFilterDelegate = delegate;
|
|
|
+ this.springSecurityFilterChain = filterChain;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void afterPropertiesSet() {
|
|
|
+ this.springSecurityFilterChain.afterPropertiesSet();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
|
+ throws IOException, ServletException {
|
|
|
+ this.doFilterDelegate.doFilter(request, response, chain);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Filter> getFilters(String url) {
|
|
|
+ return this.springSecurityFilterChain.getFilters(url);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<SecurityFilterChain> getFilterChains() {
|
|
|
+ return this.springSecurityFilterChain.getFilterChains();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
|
|
|
+ this.springSecurityFilterChain.setSecurityContextHolderStrategy(securityContextHolderStrategy);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setFilterChainValidator(FilterChainValidator filterChainValidator) {
|
|
|
+ this.springSecurityFilterChain.setFilterChainValidator(filterChainValidator);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setFilterChainDecorator(FilterChainDecorator filterChainDecorator) {
|
|
|
+ this.springSecurityFilterChain.setFilterChainDecorator(filterChainDecorator);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setFirewall(HttpFirewall firewall) {
|
|
|
+ this.springSecurityFilterChain.setFirewall(firewall);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setRequestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
|
|
|
+ this.springSecurityFilterChain.setRequestRejectedHandler(requestRejectedHandler);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Used through reflection by Spring Security's Test support to lookup the
|
|
|
+ * FilterChainProxy Filters for a specific HttpServletRequest.
|
|
|
+ * @param request
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<? extends Filter> getFilters(HttpServletRequest request) {
|
|
|
+ List<SecurityFilterChain> filterChains = this.springSecurityFilterChain.getFilterChains();
|
|
|
+ for (SecurityFilterChain chain : filterChains) {
|
|
|
+ if (chain.matches(request)) {
|
|
|
+ return chain.getFilters();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates the Filter to delegate to for doFilter
|
|
|
+ * @param filters the Filters to delegate to.
|
|
|
+ * @return the Filter for doFilter
|
|
|
+ */
|
|
|
+ private static Filter createDoFilterDelegate(List<? extends Filter> filters) {
|
|
|
+ CompositeFilter delegate = new CompositeFilter();
|
|
|
+ delegate.setFilters(filters);
|
|
|
+ return delegate;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Find the FilterChainProxy in a List of Filter
|
|
|
+ * @param filters
|
|
|
+ * @return non-null FilterChainProxy
|
|
|
+ * @throws IllegalStateException if the FilterChainProxy cannot be found
|
|
|
+ */
|
|
|
+ private static FilterChainProxy findFilterChainProxy(List<? extends Filter> filters) {
|
|
|
+ for (Filter filter : filters) {
|
|
|
+ if (filter instanceof FilterChainProxy fcp) {
|
|
|
+ return fcp;
|
|
|
+ }
|
|
|
+ if (filter instanceof DebugFilter debugFilter) {
|
|
|
+ return new CompositeFilterChainProxy(debugFilter, debugFilter.getFilterChainProxy());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new IllegalStateException("Couldn't find FilterChainProxy in " + filters);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
}
|