diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java index 5c310888db..76bd78cd4b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java @@ -23,12 +23,14 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.function.Function; import jakarta.servlet.DispatcherType; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletRegistration; import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; @@ -44,7 +46,6 @@ import org.springframework.security.web.util.matcher.RegexRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.function.SingletonSupplier; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; @@ -76,6 +77,8 @@ public abstract class AbstractRequestMatcherRegistry { AbstractRequestMatcherRegistry.class.getClassLoader()); } + private final Log logger = LogFactory.getLog(getClass()); + protected final void setApplicationContext(ApplicationContext context) { this.context = context; } @@ -209,7 +212,12 @@ public abstract class AbstractRequestMatcherRegistry { matchers.add(resolve(ant, mvc, servletContext)); } else { - matchers.add(new DeferredRequestMatcher(() -> resolve(ant, mvc, servletContext), mvc, ant)); + this.logger + .warn("The ServletRegistration API was not available at startup time. This may be due to a misconfiguration; " + + "if you are using AbstractSecurityWebApplicationInitializer, please double-check the recommendations outlined in " + + "https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#abstractsecuritywebapplicationinitializer-with-spring-mvc"); + matchers.add(new DeferredRequestMatcher((request) -> resolve(ant, mvc, request.getServletContext()), + mvc, ant)); } } return requestMatchers(matchers.toArray(new RequestMatcher[0])); @@ -466,27 +474,34 @@ public abstract class AbstractRequestMatcherRegistry { static class DeferredRequestMatcher implements RequestMatcher { - final Supplier requestMatcher; + final Function requestMatcherFactory; final AtomicReference description = new AtomicReference<>(); - DeferredRequestMatcher(Supplier resolver, RequestMatcher... candidates) { - this.requestMatcher = SingletonSupplier.of(() -> { - RequestMatcher matcher = resolver.get(); - this.description.set(matcher.toString()); - return matcher; - }); + volatile RequestMatcher requestMatcher; + + DeferredRequestMatcher(Function resolver, RequestMatcher... candidates) { + this.requestMatcherFactory = (request) -> { + if (this.requestMatcher == null) { + synchronized (this) { + if (this.requestMatcher == null) { + this.requestMatcher = resolver.apply(request); + } + } + } + return this.requestMatcher; + }; this.description.set("Deferred " + Arrays.toString(candidates)); } @Override public boolean matches(HttpServletRequest request) { - return this.requestMatcher.get().matches(request); + return this.requestMatcherFactory.apply(request).matches(request); } @Override public MatchResult matcher(HttpServletRequest request) { - return this.requestMatcher.get().matcher(request); + return this.requestMatcherFactory.apply(request).matcher(request); } @Override diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java index f718b52f03..1963dbea6b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java @@ -328,7 +328,7 @@ public class AbstractRequestMatcherRegistryTests { List requestMatchers = new ArrayList<>(); for (RequestMatcher requestMatcher : wrappedMatchers) { if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) { - requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher.get()); + requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher); } else { requestMatchers.add(requestMatcher); diff --git a/docs/modules/ROOT/pages/servlet/configuration/java.adoc b/docs/modules/ROOT/pages/servlet/configuration/java.adoc index 326eaa333b..2baac3708f 100644 --- a/docs/modules/ROOT/pages/servlet/configuration/java.adoc +++ b/docs/modules/ROOT/pages/servlet/configuration/java.adoc @@ -114,7 +114,7 @@ public class SecurityWebApplicationInitializer This onlys register the `springSecurityFilterChain` for every URL in your application. After that, we need to ensure that `WebSecurityConfig` was loaded in our existing `ApplicationInitializer`. -For example, if we use Spring MVC it is added in the `getRootConfigClasses()`: +For example, if we use Spring MVC it is added in the `getServletConfigClasses()`: [[message-web-application-inititializer-java]] [source,java] @@ -123,14 +123,42 @@ public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override - protected Class[] getRootConfigClasses() { - return new Class[] { WebSecurityConfig.class }; + protected Class[] getServletConfigClasses() { + return new Class[] { WebSecurityConfig.class, WebMvcConfig.class }; } // ... other overrides ... } ---- +The reason for this is that Spring Security needs to be able to inspect some Spring MVC configuration in order to appropriately configure xref:servlet/authorization/authorize-http-requests.adoc#_request_matchers[underlying request matchers], so they need to be in the same application context. +Placing Spring Security in `getRootConfigClasses` places it into a parent application context that may not be able to find Spring MVC's `HandlerMappingIntrospector`. + +==== Configuring for Multiple Spring MVC Dispatchers + +If desired, any Spring Security configuration that is unrelated to Spring MVC may be placed in a different configuration class like so: + +[source,java] +---- +public class MvcWebApplicationInitializer extends + AbstractAnnotationConfigDispatcherServletInitializer { + + @Override + protected Class[] getRootConfigClasses() { + return new Class[] { NonWebSecurityConfig.class }; + } + + @Override + protected Class[] getServletConfigClasses() { + return new Class[] { WebSecurityConfig.class, WebMvcConfig.class }; + } + + // ... other overrides ... +} +---- + +This can be helpful if you have multiple instances of `AbstractAnnotationConfigDispatcherServletInitializer` and don't want to duplicate the general security configuration across both of them. + [[jc-httpsecurity]] == HttpSecurity