浏览代码

Merge branch '5.8.x' into 6.0.x

Closes gh-14164
Josh Cummings 1 年之前
父节点
当前提交
c6c6eb4d66

+ 72 - 15
config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

@@ -22,6 +22,8 @@ import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
 
 import jakarta.servlet.DispatcherType;
 import jakarta.servlet.ServletContext;
@@ -42,6 +44,7 @@ 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;
 
@@ -197,34 +200,51 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 		if (servletContext == null) {
 			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
 		}
+		boolean isProgrammaticApiAvailable = isProgrammaticApiAvailable(servletContext);
+		List<RequestMatcher> matchers = new ArrayList<>();
+		for (String pattern : patterns) {
+			AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
+			MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
+			if (isProgrammaticApiAvailable) {
+				matchers.add(resolve(ant, mvc, servletContext));
+			}
+			else {
+				matchers.add(new DeferredRequestMatcher(() -> resolve(ant, mvc, servletContext), mvc, ant));
+			}
+		}
+		return requestMatchers(matchers.toArray(new RequestMatcher[0]));
+	}
+
+	private static boolean isProgrammaticApiAvailable(ServletContext servletContext) {
+		try {
+			servletContext.getServletRegistrations();
+			return true;
+		}
+		catch (UnsupportedOperationException ex) {
+			return false;
+		}
+	}
+
+	private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
 		Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
 		if (registrations.isEmpty()) {
-			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
+			return ant;
 		}
 		if (!hasDispatcherServlet(registrations)) {
-			return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
+			return ant;
 		}
 		ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
 		if (dispatcherServlet != null) {
 			if (registrations.size() == 1) {
-				return requestMatchers(createMvcMatchers(method, patterns).toArray(new RequestMatcher[0]));
+				return mvc;
 			}
-			List<RequestMatcher> matchers = new ArrayList<>();
-			for (String pattern : patterns) {
-				AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
-				MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
-				matchers.add(new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext));
-			}
-			return requestMatchers(matchers.toArray(new RequestMatcher[0]));
+			return new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext);
 		}
 		dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations);
 		if (dispatcherServlet != null) {
 			String mapping = dispatcherServlet.getMappings().iterator().next();
-			List<MvcRequestMatcher> matchers = createMvcMatchers(method, patterns);
-			for (MvcRequestMatcher matcher : matchers) {
-				matcher.setServletPath(mapping.substring(0, mapping.length() - 2));
-			}
-			return requestMatchers(matchers.toArray(new RequestMatcher[0]));
+			mvc.setServletPath(mapping.substring(0, mapping.length() - 2));
+			return mvc;
 		}
 		String errorMessage = computeErrorMessage(registrations.values());
 		throw new IllegalArgumentException(errorMessage);
@@ -444,6 +464,38 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 
 	}
 
+	static class DeferredRequestMatcher implements RequestMatcher {
+
+		final Supplier<RequestMatcher> requestMatcher;
+
+		final AtomicReference<String> description = new AtomicReference<>();
+
+		DeferredRequestMatcher(Supplier<RequestMatcher> resolver, RequestMatcher... candidates) {
+			this.requestMatcher = SingletonSupplier.of(() -> {
+				RequestMatcher matcher = resolver.get();
+				this.description.set(matcher.toString());
+				return matcher;
+			});
+			this.description.set("Deferred " + candidates);
+		}
+
+		@Override
+		public boolean matches(HttpServletRequest request) {
+			return this.requestMatcher.get().matches(request);
+		}
+
+		@Override
+		public MatchResult matcher(HttpServletRequest request) {
+			return this.requestMatcher.get().matcher(request);
+		}
+
+		@Override
+		public String toString() {
+			return this.description.get();
+		}
+
+	}
+
 	static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
 
 		private final AntPathRequestMatcher ant;
@@ -493,6 +545,11 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 			}
 		}
 
+		@Override
+		public String toString() {
+			return "DispatcherServletDelegating [" + "ant = " + this.ant + ", mvc = " + this.mvc + "]";
+		}
+
 	}
 
 }

+ 23 - 0
config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java

@@ -16,6 +16,7 @@
 
 package org.springframework.security.config.annotation.web;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import jakarta.servlet.DispatcherType;
@@ -164,6 +165,7 @@ public class AbstractRequestMatcherRegistryTests {
 
 	@Test
 	public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType() {
+		mockMvcIntrospector(true);
 		MockServletContext servletContext = new MockServletContext();
 		given(this.context.getServletContext()).willReturn(servletContext);
 		servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
@@ -182,6 +184,7 @@ public class AbstractRequestMatcherRegistryTests {
 
 	@Test
 	public void requestMatchersWhenAmbiguousServletsThenException() {
+		mockMvcIntrospector(true);
 		MockServletContext servletContext = new MockServletContext();
 		given(this.context.getServletContext()).willReturn(servletContext);
 		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
@@ -192,6 +195,7 @@ public class AbstractRequestMatcherRegistryTests {
 
 	@Test
 	public void requestMatchersWhenMultipleDispatcherServletMappingsThenException() {
+		mockMvcIntrospector(true);
 		MockServletContext servletContext = new MockServletContext();
 		given(this.context.getServletContext()).willReturn(servletContext);
 		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/mvc/*");
@@ -201,6 +205,7 @@ public class AbstractRequestMatcherRegistryTests {
 
 	@Test
 	public void requestMatchersWhenPathDispatcherServletAndOtherServletsThenException() {
+		mockMvcIntrospector(true);
 		MockServletContext servletContext = new MockServletContext();
 		given(this.context.getServletContext()).willReturn(servletContext);
 		servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
@@ -309,11 +314,29 @@ public class AbstractRequestMatcherRegistryTests {
 
 	private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
 
+		@Override
+		public List<RequestMatcher> requestMatchers(RequestMatcher... requestMatchers) {
+			return unwrap(super.requestMatchers(requestMatchers));
+		}
+
 		@Override
 		protected List<RequestMatcher> chainRequestMatchers(List<RequestMatcher> requestMatchers) {
 			return requestMatchers;
 		}
 
+		private static List<RequestMatcher> unwrap(List<RequestMatcher> wrappedMatchers) {
+			List<RequestMatcher> requestMatchers = new ArrayList<>();
+			for (RequestMatcher requestMatcher : wrappedMatchers) {
+				if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) {
+					requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher.get());
+				}
+				else {
+					requestMatchers.add(requestMatcher);
+				}
+			}
+			return requestMatchers;
+		}
+
 	}
 
 }