Browse Source

Add Opt-in PathPattern Strategy

Closes gh-16573
Josh Cummings 6 tháng trước cách đây
mục cha
commit
7d301f87d6
18 tập tin đã thay đổi với 589 bổ sung69 xóa
  1. 9 4
      config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java
  2. 59 0
      config/src/main/java/org/springframework/security/config/annotation/web/RequestMatcherFactory.java
  3. 18 19
      config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
  4. 2 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java
  5. 3 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java
  6. 3 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java
  7. 2 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurer.java
  8. 13 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java
  9. 4 4
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java
  10. 4 5
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java
  11. 39 22
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java
  12. 5 4
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java
  13. 3 3
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurer.java
  14. 133 0
      config/src/main/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBean.java
  15. 73 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java
  16. 28 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java
  17. 99 0
      config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java
  18. 92 0
      docs/modules/ROOT/pages/migration/web.adoc

+ 9 - 4
config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -219,9 +219,14 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 		}
 		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 DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
+			if (RequestMatcherFactory.usesPathPatterns()) {
+				matchers.add(RequestMatcherFactory.matcher(method, pattern));
+			}
+			else {
+				AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
+				MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
+				matchers.add(new DeferredRequestMatcher((c) -> resolve(ant, mvc, c), mvc, ant));
+			}
 		}
 		return requestMatchers(matchers.toArray(new RequestMatcher[0]));
 	}

+ 59 - 0
config/src/main/java/org/springframework/security/config/annotation/web/RequestMatcherFactory.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2025 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;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+/**
+ * This utility exists only to facilitate applications opting into using path patterns in
+ * the HttpSecurity DSL. It is for internal use only.
+ *
+ * @deprecated
+ */
+@Deprecated(forRemoval = true)
+public final class RequestMatcherFactory {
+
+	private static PathPatternRequestMatcher.Builder builder;
+
+	public static void setApplicationContext(ApplicationContext context) {
+		builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class).getIfUnique();
+	}
+
+	public static boolean usesPathPatterns() {
+		return builder != null;
+	}
+
+	public static RequestMatcher matcher(String path) {
+		return matcher(null, path);
+	}
+
+	public static RequestMatcher matcher(HttpMethod method, String path) {
+		if (builder != null) {
+			return builder.matcher(method, path);
+		}
+		return new AntPathRequestMatcher(path, (method != null) ? method.name() : null);
+	}
+
+	private RequestMatcherFactory() {
+
+	}
+
+}

+ 18 - 19
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -45,6 +45,7 @@ import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
 import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
@@ -3684,11 +3685,17 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * @see MvcRequestMatcher
 	 */
 	public HttpSecurity securityMatcher(String... patterns) {
-		if (mvcPresent) {
-			this.requestMatcher = new OrRequestMatcher(createMvcMatchers(patterns));
-			return this;
+		List<RequestMatcher> matchers = new ArrayList<>();
+		for (String pattern : patterns) {
+			if (RequestMatcherFactory.usesPathPatterns()) {
+				matchers.add(RequestMatcherFactory.matcher(pattern));
+			}
+			else {
+				RequestMatcher matcher = mvcPresent ? createMvcMatcher(pattern) : createAntMatcher(pattern);
+				matchers.add(matcher);
+			}
 		}
-		this.requestMatcher = new OrRequestMatcher(createAntMatchers(patterns));
+		this.requestMatcher = new OrRequestMatcher(matchers);
 		return this;
 	}
 
@@ -3717,15 +3724,11 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 		return HttpSecurity.this;
 	}
 
-	private List<RequestMatcher> createAntMatchers(String... patterns) {
-		List<RequestMatcher> matchers = new ArrayList<>(patterns.length);
-		for (String pattern : patterns) {
-			matchers.add(new AntPathRequestMatcher(pattern));
-		}
-		return matchers;
+	private RequestMatcher createAntMatcher(String pattern) {
+		return new AntPathRequestMatcher(pattern);
 	}
 
-	private List<RequestMatcher> createMvcMatchers(String... mvcPatterns) {
+	private RequestMatcher createMvcMatcher(String mvcPattern) {
 		ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, Object.class);
 		ObjectProvider<ObjectPostProcessor<Object>> postProcessors = getContext().getBeanProvider(type);
 		ObjectPostProcessor<Object> opp = postProcessors.getObject();
@@ -3736,13 +3739,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 		}
 		HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
 				HandlerMappingIntrospector.class);
-		List<RequestMatcher> matchers = new ArrayList<>(mvcPatterns.length);
-		for (String mvcPattern : mvcPatterns) {
-			MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
-			opp.postProcess(matcher);
-			matchers.add(matcher);
-		}
-		return matchers;
+		MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
+		opp.postProcess(matcher);
+		return matcher;
 	}
 
 	/**

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

@@ -35,6 +35,7 @@ import org.springframework.security.config.annotation.authentication.configurati
 import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
 import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
 import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
@@ -104,6 +105,7 @@ class HttpSecurityConfiguration {
 	@Bean(HTTPSECURITY_BEAN_NAME)
 	@Scope("prototype")
 	HttpSecurity httpSecurity() throws Exception {
+		RequestMatcherFactory.setApplicationContext(this.context);
 		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
 		AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
 				this.objectPostProcessor, passwordEncoder);

+ 3 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java

@@ -16,7 +16,9 @@
 
 package org.springframework.security.config.annotation.web.configurers;
 
+import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.web.AuthenticationEntryPoint;
@@ -26,7 +28,6 @@ import org.springframework.security.web.authentication.RememberMeServices;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 
 /**
@@ -234,7 +235,7 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
 
 	@Override
 	protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
-		return new AntPathRequestMatcher(loginProcessingUrl, "POST");
+		return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
 	}
 
 	/**

+ 3 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java

@@ -22,8 +22,10 @@ import java.util.List;
 
 import jakarta.servlet.http.HttpSession;
 
+import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.SecurityConfigurer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
@@ -37,7 +39,6 @@ import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuc
 import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.context.SecurityContextRepository;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
@@ -368,7 +369,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
 	}
 
 	private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
-		return new AntPathRequestMatcher(this.logoutUrl, httpMethod);
+		return RequestMatcherFactory.matcher(HttpMethod.valueOf(httpMethod), this.logoutUrl);
 	}
 
 }

+ 2 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurer.java

@@ -17,9 +17,9 @@
 package org.springframework.security.config.annotation.web.configurers;
 
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.web.RequestMatcherRedirectFilter;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.util.Assert;
 
 /**
@@ -55,7 +55,7 @@ public final class PasswordManagementConfigurer<B extends HttpSecurityBuilder<B>
 	@Override
 	public void configure(B http) throws Exception {
 		RequestMatcherRedirectFilter changePasswordFilter = new RequestMatcherRedirectFilter(
-				new AntPathRequestMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
+				RequestMatcherFactory.matcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN), this.changePasswordPage);
 		http.addFilterBefore(postProcess(changePasswordFilter), UsernamePasswordAuthenticationFilter.class);
 	}
 

+ 13 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java

@@ -21,8 +21,10 @@ import java.util.Collections;
 import java.util.List;
 
 import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
 import org.springframework.security.web.savedrequest.NullRequestCache;
@@ -140,13 +142,13 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
 
 	@SuppressWarnings("unchecked")
 	private RequestMatcher createDefaultSavedRequestMatcher(H http) {
-		RequestMatcher notFavIcon = new NegatedRequestMatcher(new AntPathRequestMatcher("/**/favicon.*"));
+		RequestMatcher notFavIcon = new NegatedRequestMatcher(getFaviconRequestMatcher());
 		RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
 				new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
 		boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null;
 		List<RequestMatcher> matchers = new ArrayList<>();
 		if (isCsrfEnabled) {
-			RequestMatcher getRequests = new AntPathRequestMatcher("/**", "GET");
+			RequestMatcher getRequests = RequestMatcherFactory.matcher(HttpMethod.GET, "/**");
 			matchers.add(0, getRequests);
 		}
 		matchers.add(notFavIcon);
@@ -167,4 +169,13 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
 		return new NegatedRequestMatcher(mediaRequest);
 	}
 
+	private RequestMatcher getFaviconRequestMatcher() {
+		if (RequestMatcherFactory.usesPathPatterns()) {
+			return RequestMatcherFactory.matcher("/favicon.*");
+		}
+		else {
+			return new AntPathRequestMatcher("/**/favicon.*");
+		}
+	}
+
 }

+ 4 - 4
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

@@ -38,6 +38,7 @@ import org.springframework.core.ResolvableType;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -91,7 +92,6 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageGenera
 import org.springframework.security.web.csrf.CsrfToken;
 import org.springframework.security.web.savedrequest.RequestCache;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AnyRequestMatcher;
 import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -431,7 +431,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 
 	@Override
 	protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
-		return new AntPathRequestMatcher(loginProcessingUrl);
+		return RequestMatcherFactory.matcher(loginProcessingUrl);
 	}
 
 	private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
@@ -569,8 +569,8 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	}
 
 	private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
-		RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
-		RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
+		RequestMatcher loginPageMatcher = RequestMatcherFactory.matcher(this.getLoginPage());
+		RequestMatcher faviconMatcher = RequestMatcherFactory.matcher("/favicon.ico");
 		RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
 		RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
 				new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);

+ 4 - 5
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java

@@ -31,6 +31,7 @@ import org.springframework.security.authentication.ott.OneTimeTokenAuthenticatio
 import org.springframework.security.authentication.ott.OneTimeTokenService;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
@@ -56,8 +57,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
-import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
-
 /**
  * An {@link AbstractHttpConfigurer} for One-Time Token Login.
  *
@@ -163,7 +162,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
 	private void configureOttGenerateFilter(H http) {
 		GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(),
 				getOneTimeTokenGenerationSuccessHandler());
-		generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
+		generateFilter.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.POST, this.tokenGeneratingUrl));
 		generateFilter.setRequestResolver(getGenerateRequestResolver());
 		http.addFilter(postProcess(generateFilter));
 		http.addFilter(DefaultResourcesFilter.css());
@@ -190,7 +189,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
 		}
 		DefaultOneTimeTokenSubmitPageGeneratingFilter submitPage = new DefaultOneTimeTokenSubmitPageGeneratingFilter();
 		submitPage.setResolveHiddenInputs(this::hiddenInputs);
-		submitPage.setRequestMatcher(antMatcher(HttpMethod.GET, this.defaultSubmitPageUrl));
+		submitPage.setRequestMatcher(RequestMatcherFactory.matcher(HttpMethod.GET, this.defaultSubmitPageUrl));
 		submitPage.setLoginProcessingUrl(this.getLoginProcessingUrl());
 		http.addFilter(postProcess(submitPage));
 	}
@@ -207,7 +206,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
 
 	@Override
 	protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
-		return antMatcher(HttpMethod.POST, loginProcessingUrl);
+		return RequestMatcherFactory.matcher(HttpMethod.POST, loginProcessingUrl);
 	}
 
 	/**

+ 39 - 22
config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

@@ -28,6 +28,7 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -56,7 +57,6 @@ import org.springframework.security.web.authentication.DelegatingAuthenticationE
 import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
 import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
@@ -127,15 +127,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 
 	private String[] authenticationRequestParams = { "registrationId={registrationId}" };
 
-	private RequestMatcher authenticationRequestMatcher = RequestMatchers.anyOf(
-			new AntPathRequestMatcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
-			new AntPathQueryRequestMatcher(this.authenticationRequestUri, this.authenticationRequestParams));
+	private RequestMatcher authenticationRequestMatcher;
 
 	private Saml2AuthenticationRequestResolver authenticationRequestResolver;
 
-	private RequestMatcher loginProcessingUrl = RequestMatchers.anyOf(
-			new AntPathRequestMatcher(Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI),
-			new AntPathRequestMatcher("/login/saml2/sso"));
+	private RequestMatcher loginProcessingUrl;
 
 	private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
 
@@ -238,8 +234,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		this.authenticationRequestUri = parts[0];
 		this.authenticationRequestParams = new String[parts.length - 1];
 		System.arraycopy(parts, 1, this.authenticationRequestParams, 0, parts.length - 1);
-		this.authenticationRequestMatcher = new AntPathQueryRequestMatcher(this.authenticationRequestUri,
-				this.authenticationRequestParams);
+		this.authenticationRequestMatcher = new PathQueryRequestMatcher(
+				RequestMatcherFactory.matcher(this.authenticationRequestUri), this.authenticationRequestParams);
 		return this;
 	}
 
@@ -256,13 +252,13 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	@Override
 	public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
 		Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
-		this.loginProcessingUrl = new AntPathRequestMatcher(loginProcessingUrl);
+		this.loginProcessingUrl = RequestMatcherFactory.matcher(loginProcessingUrl);
 		return this;
 	}
 
 	@Override
 	protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
-		return new AntPathRequestMatcher(loginProcessingUrl);
+		return RequestMatcherFactory.matcher(loginProcessingUrl);
 	}
 
 	/**
@@ -284,7 +280,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		relyingPartyRegistrationRepository(http);
 		this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http));
 		this.saml2WebSsoAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
-		this.saml2WebSsoAuthenticationFilter.setRequiresAuthenticationRequestMatcher(this.loginProcessingUrl);
+		this.saml2WebSsoAuthenticationFilter.setRequiresAuthenticationRequestMatcher(getLoginProcessingEndpoint());
 		setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter);
 		setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter);
 		if (StringUtils.hasText(this.loginPage)) {
@@ -340,8 +336,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	}
 
 	private AuthenticationEntryPoint getLoginEntryPoint(B http, String providerLoginPage) {
-		RequestMatcher loginPageMatcher = new AntPathRequestMatcher(this.getLoginPage());
-		RequestMatcher faviconMatcher = new AntPathRequestMatcher("/favicon.ico");
+		RequestMatcher loginPageMatcher = RequestMatcherFactory.matcher(this.getLoginPage());
+		RequestMatcher faviconMatcher = RequestMatcherFactory.matcher("/favicon.ico");
 		RequestMatcher defaultEntryPointMatcher = this.getAuthenticationEntryPointMatcher(http);
 		RequestMatcher defaultLoginPageMatcher = new AndRequestMatcher(
 				new OrRequestMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
@@ -376,17 +372,38 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		if (USE_OPENSAML_5) {
 			OpenSaml5AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml5AuthenticationRequestResolver(
 					relyingPartyRegistrationRepository(http));
-			openSamlAuthenticationRequestResolver.setRequestMatcher(this.authenticationRequestMatcher);
+			openSamlAuthenticationRequestResolver.setRequestMatcher(getAuthenticationRequestMatcher());
 			return openSamlAuthenticationRequestResolver;
 		}
 		else {
 			OpenSaml4AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
 					relyingPartyRegistrationRepository(http));
-			openSamlAuthenticationRequestResolver.setRequestMatcher(this.authenticationRequestMatcher);
+			openSamlAuthenticationRequestResolver.setRequestMatcher(getAuthenticationRequestMatcher());
 			return openSamlAuthenticationRequestResolver;
 		}
 	}
 
+	private RequestMatcher getAuthenticationRequestMatcher() {
+		if (this.authenticationRequestMatcher == null) {
+			this.authenticationRequestMatcher = RequestMatchers.anyOf(
+					RequestMatcherFactory
+						.matcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
+					new PathQueryRequestMatcher(RequestMatcherFactory.matcher(this.authenticationRequestUri),
+							this.authenticationRequestParams));
+		}
+		return this.authenticationRequestMatcher;
+	}
+
+	private RequestMatcher getLoginProcessingEndpoint() {
+		if (this.loginProcessingUrl == null) {
+			this.loginProcessingUrl = RequestMatchers.anyOf(
+					RequestMatcherFactory.matcher(Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI),
+					RequestMatcherFactory.matcher("/login/saml2/sso"));
+		}
+
+		return this.loginProcessingUrl;
+	}
+
 	private AuthenticationConverter getAuthenticationConverter(B http) {
 		if (this.authenticationConverter != null) {
 			return this.authenticationConverter;
@@ -407,7 +424,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 			OpenSaml5AuthenticationTokenConverter converter = new OpenSaml5AuthenticationTokenConverter(
 					this.relyingPartyRegistrationRepository);
 			converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
-			converter.setRequestMatcher(this.loginProcessingUrl);
+			converter.setRequestMatcher(getLoginProcessingEndpoint());
 			return converter;
 		}
 		authenticationConverterBean = getBeanOrNull(http, OpenSaml4AuthenticationTokenConverter.class);
@@ -417,7 +434,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(
 				this.relyingPartyRegistrationRepository);
 		converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
-		converter.setRequestMatcher(this.loginProcessingUrl);
+		converter.setRequestMatcher(getLoginProcessingEndpoint());
 		return converter;
 	}
 
@@ -441,7 +458,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		if (csrf == null) {
 			return;
 		}
-		csrf.ignoringRequestMatchers(this.loginProcessingUrl);
+		csrf.ignoringRequestMatchers(getLoginProcessingEndpoint());
 	}
 
 	private void initDefaultLoginFilter(B http) {
@@ -509,13 +526,13 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		}
 	}
 
-	static class AntPathQueryRequestMatcher implements RequestMatcher {
+	static class PathQueryRequestMatcher implements RequestMatcher {
 
 		private final RequestMatcher matcher;
 
-		AntPathQueryRequestMatcher(String path, String... params) {
+		PathQueryRequestMatcher(RequestMatcher pathMatcher, String... params) {
 			List<RequestMatcher> matchers = new ArrayList<>();
-			matchers.add(new AntPathRequestMatcher(path));
+			matchers.add(pathMatcher);
 			for (String param : params) {
 				String[] parts = param.split("=");
 				if (parts.length == 1) {

+ 5 - 4
config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java

@@ -23,9 +23,11 @@ import jakarta.servlet.http.HttpServletRequest;
 import org.opensaml.core.Version;
 
 import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
@@ -64,7 +66,6 @@ import org.springframework.security.web.csrf.CsrfFilter;
 import org.springframework.security.web.csrf.CsrfLogoutHandler;
 import org.springframework.security.web.csrf.CsrfTokenRepository;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 
@@ -304,19 +305,19 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
 	}
 
 	private RequestMatcher createLogoutMatcher() {
-		RequestMatcher logout = new AntPathRequestMatcher(this.logoutUrl, "POST");
+		RequestMatcher logout = RequestMatcherFactory.matcher(HttpMethod.POST, this.logoutUrl);
 		RequestMatcher saml2 = new Saml2RequestMatcher(getSecurityContextHolderStrategy());
 		return new AndRequestMatcher(logout, saml2);
 	}
 
 	private RequestMatcher createLogoutRequestMatcher() {
-		RequestMatcher logout = new AntPathRequestMatcher(this.logoutRequestConfigurer.logoutUrl);
+		RequestMatcher logout = RequestMatcherFactory.matcher(this.logoutRequestConfigurer.logoutUrl);
 		RequestMatcher samlRequest = new ParameterRequestMatcher("SAMLRequest");
 		return new AndRequestMatcher(logout, samlRequest);
 	}
 
 	private RequestMatcher createLogoutResponseMatcher() {
-		RequestMatcher logout = new AntPathRequestMatcher(this.logoutResponseConfigurer.logoutUrl);
+		RequestMatcher logout = RequestMatcherFactory.matcher(this.logoutResponseConfigurer.logoutUrl);
 		RequestMatcher samlResponse = new ParameterRequestMatcher("SAMLResponse");
 		return new AndRequestMatcher(logout, samlResponse);
 	}

+ 3 - 3
config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurer.java

@@ -22,6 +22,7 @@ import org.opensaml.core.Version;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.RequestMatcherFactory;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver;
@@ -32,7 +33,6 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
 import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter;
 import org.springframework.security.saml2.provider.service.web.metadata.RequestMatcherMetadataResponseResolver;
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.util.Assert;
 
 /**
@@ -111,12 +111,12 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
 			if (USE_OPENSAML_5) {
 				RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(
 						registrations, new OpenSaml5MetadataResolver());
-				metadata.setRequestMatcher(new AntPathRequestMatcher(metadataUrl));
+				metadata.setRequestMatcher(RequestMatcherFactory.matcher(metadataUrl));
 				return metadata;
 			}
 			RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(registrations,
 					new OpenSaml4MetadataResolver());
-			metadata.setRequestMatcher(new AntPathRequestMatcher(metadataUrl));
+			metadata.setRequestMatcher(RequestMatcherFactory.matcher(metadataUrl));
 			return metadata;
 		};
 		return this;

+ 133 - 0
config/src/main/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBean.java

@@ -0,0 +1,133 @@
+/*
+ * Copyright 2002-2025 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.web;
+
+import reactor.util.annotation.NonNull;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
+import org.springframework.web.util.pattern.PathPatternParser;
+
+/**
+ * Use this factory bean to configure the {@link PathPatternRequestMatcher.Builder} bean
+ * used to create request matchers in {@link AuthorizeHttpRequestsConfigurer} and other
+ * parts of the DSL.
+ *
+ * @author Josh Cummings
+ * @since 6.5
+ */
+public final class PathPatternRequestMatcherBuilderFactoryBean implements
+		FactoryBean<PathPatternRequestMatcher.Builder>, ApplicationContextAware, BeanNameAware, BeanFactoryAware {
+
+	static final String MVC_PATTERN_PARSER_BEAN_NAME = "mvcPatternParser";
+
+	private final PathPatternParser parser;
+
+	private ApplicationContext context;
+
+	private String beanName;
+
+	private ConfigurableListableBeanFactory beanFactory;
+
+	/**
+	 * Construct this factory bean using the default {@link PathPatternParser}
+	 *
+	 * <p>
+	 * If you are using Spring MVC, it will use the Spring MVC instance.
+	 */
+	public PathPatternRequestMatcherBuilderFactoryBean() {
+		this(null);
+	}
+
+	/**
+	 * Construct this factory bean using this {@link PathPatternParser}.
+	 *
+	 * <p>
+	 * If you are using Spring MVC, it is likely incorrect to call this constructor.
+	 * Please call the default constructor instead.
+	 * @param parser the {@link PathPatternParser} to use
+	 */
+	public PathPatternRequestMatcherBuilderFactoryBean(PathPatternParser parser) {
+		this.parser = parser;
+	}
+
+	@Override
+	public PathPatternRequestMatcher.Builder getObject() throws Exception {
+		if (!this.context.containsBean(MVC_PATTERN_PARSER_BEAN_NAME)) {
+			PathPatternParser parser = (this.parser != null) ? this.parser : PathPatternParser.defaultInstance;
+			return PathPatternRequestMatcher.withPathPatternParser(parser);
+		}
+		PathPatternParser mvc = this.context.getBean(MVC_PATTERN_PARSER_BEAN_NAME, PathPatternParser.class);
+		PathPatternParser parser = (this.parser != null) ? this.parser : mvc;
+		if (mvc.equals(parser)) {
+			return PathPatternRequestMatcher.withPathPatternParser(parser);
+		}
+		throw new IllegalArgumentException("Spring Security and Spring MVC must use the same path pattern parser. "
+				+ "To have Spring Security use Spring MVC's [" + describe(mvc, MVC_PATTERN_PARSER_BEAN_NAME)
+				+ "] simply publish this bean [" + describe(this, this.beanName) + "] using its default constructor");
+	}
+
+	@Override
+	public Class<?> getObjectType() {
+		return PathPatternRequestMatcher.Builder.class;
+	}
+
+	@Override
+	public void setApplicationContext(ApplicationContext context) throws BeansException {
+		this.context = context;
+	}
+
+	@Override
+	public void setBeanName(@NonNull String name) {
+		this.beanName = name;
+	}
+
+	@Override
+	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+		if (beanFactory instanceof ConfigurableListableBeanFactory listable) {
+			this.beanFactory = listable;
+		}
+	}
+
+	private String describe(Object bean, String name) {
+		String text = bean.getClass().getSimpleName();
+		if (name == null) {
+			return text;
+		}
+		text += "defined as '" + name + "'";
+		if (this.beanFactory == null) {
+			return text;
+		}
+		BeanDefinition bd = this.beanFactory.getBeanDefinition(name);
+		String description = bd.getResourceDescription();
+		if (description == null) {
+			return text;
+		}
+		text += " in [" + description + "]";
+		return text;
+	}
+
+}

+ 73 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

@@ -51,6 +51,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
 import org.springframework.security.config.observation.SecurityObservationSettings;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -64,6 +65,7 @@ import org.springframework.security.web.access.intercept.AuthorizationFilter;
 import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
 import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
 import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.RequestPostProcessor;
@@ -72,6 +74,7 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.DispatcherServlet;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 
@@ -667,6 +670,26 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		verifyNoInteractions(handler);
 	}
 
+	@Test
+	public void requestMatchersWhenMultipleDispatcherServletsAndPathBeanThenAllows() throws Exception {
+		this.spring.register(MvcRequestMatcherBuilderConfig.class, BasicController.class)
+			.postProcessor((context) -> context.getServletContext()
+				.addServlet("otherDispatcherServlet", DispatcherServlet.class)
+				.addMapping("/mvc"))
+			.autowire();
+		this.mvc.perform(get("/mvc/path").servletPath("/mvc").with(user("user"))).andExpect(status().isOk());
+		this.mvc.perform(get("/mvc/path").servletPath("/mvc").with(user("user").roles("DENIED")))
+			.andExpect(status().isForbidden());
+		this.mvc.perform(get("/path").with(user("user"))).andExpect(status().isForbidden());
+	}
+
+	@Test
+	public void requestMatchersWhenFactoryBeanThenAuthorizes() throws Exception {
+		this.spring.register(PathPatternFactoryBeanConfig.class).autowire();
+		this.mvc.perform(get("/path/resource")).andExpect(status().isUnauthorized());
+		this.mvc.perform(get("/path/resource").with(user("user").roles("USER"))).andExpect(status().isNotFound());
+	}
+
 	@Configuration
 	@EnableWebSecurity
 	static class GrantedAuthorityDefaultHasRoleConfig {
@@ -1262,6 +1285,10 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		void rootPost() {
 		}
 
+		@GetMapping("/path")
+		void path() {
+		}
+
 	}
 
 	@Configuration
@@ -1317,4 +1344,50 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 	}
 
+	@Configuration
+	@EnableWebSecurity
+	@EnableWebMvc
+	static class MvcRequestMatcherBuilderConfig {
+
+		@Bean
+		SecurityFilterChain security(HttpSecurity http) throws Exception {
+			PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults().servletPath("/mvc");
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.requestMatchers(mvc.matcher("/path/**")).hasRole("USER")
+				)
+				.httpBasic(withDefaults());
+			// @formatter:on
+
+			return http.build();
+		}
+
+	}
+
+	@Configuration
+	@EnableWebSecurity
+	@EnableWebMvc
+	static class PathPatternFactoryBeanConfig {
+
+		@Bean
+		SecurityFilterChain security(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.requestMatchers("/path/**").hasRole("USER")
+				)
+				.httpBasic(withDefaults());
+			// @formatter:on
+
+			return http.build();
+		}
+
+		@Bean
+		PathPatternRequestMatcherBuilderFactoryBean pathPatternFactoryBean() {
+			return new PathPatternRequestMatcherBuilderFactoryBean();
+		}
+
+	}
+
 }

+ 28 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java

@@ -34,6 +34,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
@@ -291,6 +292,22 @@ public class RequestCacheConfigurerTests {
 		this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
 	}
 
+	@Test
+	public void getWhenPathPatternFactoryBeanThenFaviconIcoRedirectsToRoot() throws Exception {
+		this.spring
+			.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class, PathPatternFactoryBeanConfig.class)
+			.autowire();
+		// @formatter:off
+		MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico"))
+				.andExpect(redirectedUrl("http://localhost/login"))
+				.andReturn()
+				.getRequest()
+				.getSession();
+		// @formatter:on
+		// ignores favicon.ico
+		this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
+	}
+
 	private static RequestBuilder formLogin(MockHttpSession session) {
 		// @formatter:off
 		return post("/login")
@@ -470,4 +487,15 @@ public class RequestCacheConfigurerTests {
 
 	}
 
+	@Configuration
+	@EnableWebSecurity
+	static class PathPatternFactoryBeanConfig {
+
+		@Bean
+		PathPatternRequestMatcherBuilderFactoryBean factoryBean() {
+			return new PathPatternRequestMatcherBuilderFactoryBean();
+		}
+
+	}
+
 }

+ 99 - 0
config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2025 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.web;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
+import org.springframework.web.util.pattern.PathPatternParser;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class PathPatternRequestMatcherBuilderFactoryBeanTests {
+
+	GenericApplicationContext context;
+
+	@BeforeEach
+	void setUp() {
+		this.context = new GenericApplicationContext();
+	}
+
+	@Test
+	void getObjectWhenDefaultsThenBuilder() throws Exception {
+		factoryBean().getObject();
+	}
+
+	@Test
+	void getObjectWhenMvcPatternParserThenUses() throws Exception {
+		PathPatternParser mvc = registerMvcPatternParser();
+		PathPatternRequestMatcher.Builder builder = factoryBean().getObject();
+		builder.matcher("/path/**");
+		verify(mvc).parse("/path/**");
+	}
+
+	@Test
+	void getObjectWhenPathPatternParserThenUses() throws Exception {
+		PathPatternParser parser = mock(PathPatternParser.class);
+		PathPatternRequestMatcher.Builder builder = factoryBean(parser).getObject();
+		builder.matcher("/path/**");
+		verify(parser).parse("/path/**");
+	}
+
+	@Test
+	void getObjectWhenMvcAndPathPatternParserConflictThenIllegalArgument() {
+		registerMvcPatternParser();
+		PathPatternParser parser = mock(PathPatternParser.class);
+		assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> factoryBean(parser).getObject());
+	}
+
+	@Test
+	void getObjectWhenMvcAndPathPatternParserAgreeThenUses() throws Exception {
+		PathPatternParser mvc = registerMvcPatternParser();
+		PathPatternRequestMatcher.Builder builder = factoryBean(mvc).getObject();
+		builder.matcher("/path/**");
+		verify(mvc).parse("/path/**");
+	}
+
+	PathPatternRequestMatcherBuilderFactoryBean factoryBean() {
+		PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean();
+		factoryBean.setApplicationContext(this.context);
+		return factoryBean;
+	}
+
+	PathPatternRequestMatcherBuilderFactoryBean factoryBean(PathPatternParser parser) {
+		PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(
+				parser);
+		factoryBean.setApplicationContext(this.context);
+		return factoryBean;
+	}
+
+	PathPatternParser registerMvcPatternParser() {
+		PathPatternParser mvc = mock(PathPatternParser.class);
+		this.context.registerBean(PathPatternRequestMatcherBuilderFactoryBean.MVC_PATTERN_PARSER_BEAN_NAME,
+				PathPatternParser.class, () -> mvc);
+		this.context.refresh();
+		return mvc;
+	}
+
+}

+ 92 - 0
docs/modules/ROOT/pages/migration/web.adoc

@@ -0,0 +1,92 @@
+= Web Migrations
+
+[[use-path-pattern]]
+== Use PathPatternRequestMatcher by Default
+
+In Spring Security 7, `AntPathRequestMatcher` and `MvcRequestMatcher` are no longer supported and the Java DSL requires that all URIs be absolute (less any context root).
+At that time, Spring Security 7 will use `PathPatternRequestMatcher` by default.
+
+To check how prepared you are for this change, you can publish this bean:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
+	return new PathPatternRequestMatcherBuilderFactoryBean();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun requestMatcherBuilder(): PathPatternRequestMatcherBuilderFactoryBean {
+    return PathPatternRequestMatcherBuilderFactoryBean()
+}
+----
+======
+
+This will tell the Spring Security DSL to use `PathPatternRequestMatcher` for all request matchers that it constructs.
+
+In the event that you are directly constructing an object (as opposed to having the DSL construct it) that has a `setRequestMatcher` method. you should also proactively specify a `PathPatternRequestMatcher` there as well.
+
+For example, in the case of `LogoutFilter`, it constructs an `AntPathRequestMatcher` in Spring Security 6:
+
+[method,java]
+----
+private RequestMatcher logoutUrl = new AntPathRequestMatcher("/logout");
+----
+
+and will change this to a `PathPatternRequestMatcher` in 7:
+
+[method,java]
+----
+private RequestMatcher logoutUrl = PathPatternRequestMatcher.path().matcher("/logout");
+----
+
+If you are constructing your own `LogoutFilter`, consider calling `setLogoutRequestMatcher` to provide this `PathPatternRequestMatcher` in advance.
+
+== Include the Servlet Path Prefix in Authorization Rules
+
+For many applications <<use-path-pattern, the above>> will make no difference since most commonly all URIs listed are matched by the default servlet.
+
+However, if you have other servlets with servlet path prefixes, xref:servlet/authorization/authorize-http-requests.adoc[then these paths now need to be supplied separately].
+
+For example, if I have a Spring MVC controller with `@RequestMapping("/orders")` and my MVC application is deployed to `/mvc` (instead of the default servlet), then the URI for this endpoint is `/mvc/orders`.
+Historically, the Java DSL hasn't had a simple way to specify the servlet path prefix and Spring Security attempted to infer it.
+
+Over time, we learned that these inference would surprise developers.
+Instead of taking this responsibility away from developers, now it is simpler to specify the servlet path prefix like so:
+
+[method,java]
+----
+PathPatternRequestParser.Builder servlet = PathPatternRequestParser.servletPath("/mvc");
+http
+    .authorizeHttpRequests((authorize) -> authorize
+        .requestMatchers(servlet.pattern("/orders/**").matcher()).authenticated()
+    )
+----
+
+
+For paths that belong to the default servlet, use `PathPatternRequestParser.path()` instead:
+
+[method,java]
+----
+PathPatternRequestParser.Builder request = PathPatternRequestParser.path();
+http
+    .authorizeHttpRequests((authorize) -> authorize
+        .requestMatchers(request.pattern("/js/**").matcher()).authenticated()
+    )
+----
+
+Note that this doesn't address every kind of servlet since not all servlets have a path prefix.
+For example, expressions that match the JSP Servlet might use an ant pattern `/**/*.jsp`.
+
+There is not yet a general-purpose replacement for these, and so you are encouraged to use `RegexRequestMatcher`, like so:  `regexMatcher("\\.jsp$")`.
+
+For many applications this will make no difference since most commonly all URIs listed are matched by the default servlet.