Skip to content

Commit 4ca5468

Browse files
committed
Defer requestMatchers Validation to Runtime
Closes gh-13794
1 parent a7da949 commit 4ca5468

File tree

2 files changed

+95
-15
lines changed

2 files changed

+95
-15
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.LinkedHashMap;
2323
import java.util.List;
2424
import java.util.Map;
25+
import java.util.concurrent.atomic.AtomicReference;
26+
import java.util.function.Supplier;
2527

2628
import javax.servlet.DispatcherType;
2729
import javax.servlet.ServletContext;
@@ -42,6 +44,7 @@
4244
import org.springframework.security.web.util.matcher.RequestMatcher;
4345
import org.springframework.util.Assert;
4446
import org.springframework.util.ClassUtils;
47+
import org.springframework.util.function.SingletonSupplier;
4548
import org.springframework.web.context.WebApplicationContext;
4649
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
4750

@@ -315,34 +318,51 @@ public C requestMatchers(HttpMethod method, String... patterns) {
315318
if (servletContext == null) {
316319
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
317320
}
321+
boolean isProgrammaticApiAvailable = isProgrammaticApiAvailable(servletContext);
322+
List<RequestMatcher> matchers = new ArrayList<>();
323+
for (String pattern : patterns) {
324+
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
325+
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
326+
if (isProgrammaticApiAvailable) {
327+
matchers.add(resolve(ant, mvc, servletContext));
328+
}
329+
else {
330+
matchers.add(new DeferredRequestMatcher(() -> resolve(ant, mvc, servletContext), mvc, ant));
331+
}
332+
}
333+
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
334+
}
335+
336+
private static boolean isProgrammaticApiAvailable(ServletContext servletContext) {
337+
try {
338+
servletContext.getServletRegistrations();
339+
return true;
340+
}
341+
catch (UnsupportedOperationException ex) {
342+
return false;
343+
}
344+
}
345+
346+
private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
318347
Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
319348
if (registrations.isEmpty()) {
320-
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
349+
return ant;
321350
}
322351
if (!hasDispatcherServlet(registrations)) {
323-
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
352+
return ant;
324353
}
325354
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
326355
if (dispatcherServlet != null) {
327356
if (registrations.size() == 1) {
328-
return requestMatchers(createMvcMatchers(method, patterns).toArray(new RequestMatcher[0]));
357+
return mvc;
329358
}
330-
List<RequestMatcher> matchers = new ArrayList<>();
331-
for (String pattern : patterns) {
332-
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
333-
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
334-
matchers.add(new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext));
335-
}
336-
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
359+
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext);
337360
}
338361
dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations);
339362
if (dispatcherServlet != null) {
340363
String mapping = dispatcherServlet.getMappings().iterator().next();
341-
List<MvcRequestMatcher> matchers = createMvcMatchers(method, patterns);
342-
for (MvcRequestMatcher matcher : matchers) {
343-
matcher.setServletPath(mapping.substring(0, mapping.length() - 2));
344-
}
345-
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
364+
mvc.setServletPath(mapping.substring(0, mapping.length() - 2));
365+
return mvc;
346366
}
347367
String errorMessage = computeErrorMessage(registrations.values());
348368
throw new IllegalArgumentException(errorMessage);
@@ -562,6 +582,38 @@ static List<RequestMatcher> regexMatchers(String... regexPatterns) {
562582

563583
}
564584

585+
static class DeferredRequestMatcher implements RequestMatcher {
586+
587+
final Supplier<RequestMatcher> requestMatcher;
588+
589+
final AtomicReference<String> description = new AtomicReference<>();
590+
591+
DeferredRequestMatcher(Supplier<RequestMatcher> resolver, RequestMatcher... candidates) {
592+
this.requestMatcher = SingletonSupplier.of(() -> {
593+
RequestMatcher matcher = resolver.get();
594+
this.description.set(matcher.toString());
595+
return matcher;
596+
});
597+
this.description.set("Deferred " + candidates);
598+
}
599+
600+
@Override
601+
public boolean matches(HttpServletRequest request) {
602+
return this.requestMatcher.get().matches(request);
603+
}
604+
605+
@Override
606+
public MatchResult matcher(HttpServletRequest request) {
607+
return this.requestMatcher.get().matcher(request);
608+
}
609+
610+
@Override
611+
public String toString() {
612+
return this.description.get();
613+
}
614+
615+
}
616+
565617
static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
566618

567619
private final AntPathRequestMatcher ant;
@@ -611,6 +663,11 @@ private boolean isDispatcherServlet(ServletRegistration registration) {
611663
}
612664
}
613665

666+
@Override
667+
public String toString() {
668+
return "DispatcherServletDelegating [" + "ant = " + this.ant + ", mvc = " + this.mvc + "]";
669+
}
670+
614671
}
615672

616673
}

config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.Field;
2020
import java.lang.reflect.Modifier;
21+
import java.util.ArrayList;
2122
import java.util.List;
2223

2324
import javax.servlet.DispatcherType;
@@ -202,6 +203,7 @@ public void requestMatchersWhenMvcPresentInClassPathAndMvcIntrospectorBeanNotAva
202203

203204
@Test
204205
public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType() {
206+
mockMvcIntrospector(true);
205207
MockServletContext servletContext = new MockServletContext();
206208
given(this.context.getServletContext()).willReturn(servletContext);
207209
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
@@ -220,6 +222,7 @@ public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType(
220222

221223
@Test
222224
public void requestMatchersWhenAmbiguousServletsThenException() {
225+
mockMvcIntrospector(true);
223226
MockServletContext servletContext = new MockServletContext();
224227
given(this.context.getServletContext()).willReturn(servletContext);
225228
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
@@ -230,6 +233,7 @@ public void requestMatchersWhenAmbiguousServletsThenException() {
230233

231234
@Test
232235
public void requestMatchersWhenMultipleDispatcherServletMappingsThenException() {
236+
mockMvcIntrospector(true);
233237
MockServletContext servletContext = new MockServletContext();
234238
given(this.context.getServletContext()).willReturn(servletContext);
235239
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/mvc/*");
@@ -239,6 +243,7 @@ public void requestMatchersWhenMultipleDispatcherServletMappingsThenException()
239243

240244
@Test
241245
public void requestMatchersWhenPathDispatcherServletAndOtherServletsThenException() {
246+
mockMvcIntrospector(true);
242247
MockServletContext servletContext = new MockServletContext();
243248
given(this.context.getServletContext()).willReturn(servletContext);
244249
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
@@ -366,11 +371,29 @@ public List<RequestMatcher> mvcMatchers(HttpMethod method, String... mvcPatterns
366371
return null;
367372
}
368373

374+
@Override
375+
public List<RequestMatcher> requestMatchers(RequestMatcher... requestMatchers) {
376+
return unwrap(super.requestMatchers(requestMatchers));
377+
}
378+
369379
@Override
370380
protected List<RequestMatcher> chainRequestMatchers(List<RequestMatcher> requestMatchers) {
371381
return requestMatchers;
372382
}
373383

384+
private static List<RequestMatcher> unwrap(List<RequestMatcher> wrappedMatchers) {
385+
List<RequestMatcher> requestMatchers = new ArrayList<>();
386+
for (RequestMatcher requestMatcher : wrappedMatchers) {
387+
if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) {
388+
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher.get());
389+
}
390+
else {
391+
requestMatchers.add(requestMatcher);
392+
}
393+
}
394+
return requestMatchers;
395+
}
396+
374397
}
375398

376399
}

0 commit comments

Comments
 (0)