Browse Source

Merge branch '5.8.x'

Closes gh-11347 in 6.0.x
Closes gh-11945
Marcus Da Coregio 2 years ago
parent
commit
ad2abd39dc
18 changed files with 1391 additions and 38 deletions
  1. 102 1
      config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java
  2. 336 3
      config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
  3. 14 4
      config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java
  4. 2 2
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
  5. 10 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
  6. 40 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
  7. 13 1
      config/src/main/kotlin/org/springframework/security/config/annotation/web/CsrfDsl.kt
  8. 105 1
      config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java
  9. 54 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java
  10. 567 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java
  11. 33 0
      config/src/test/kotlin/org/springframework/security/config/annotation/web/CsrfDslTests.kt
  12. 6 5
      docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc
  13. 2 2
      docs/modules/ROOT/pages/servlet/authorization/expression-based.adoc
  14. 20 14
      docs/modules/ROOT/pages/servlet/integrations/mvc.adoc
  15. 2 2
      docs/modules/ROOT/pages/servlet/integrations/websocket.adoc
  16. 1 1
      docs/modules/ROOT/pages/whats-new.adoc
  17. 56 1
      web/src/main/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.java
  18. 28 1
      web/src/test/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcherTests.java

+ 102 - 1
config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatche
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 
 
 /**
 /**
@@ -50,12 +51,21 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 
 
 	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
 	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
 
 
+	private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
+
+	private static final boolean mvcPresent;
+
 	private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
 	private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
 
 
 	private ApplicationContext context;
 	private ApplicationContext context;
 
 
 	private boolean anyRequestConfigured = false;
 	private boolean anyRequestConfigured = false;
 
 
+	static {
+		mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
+				AbstractRequestMatcherRegistry.class.getClassLoader());
+	}
+
 	protected final void setApplicationContext(ApplicationContext context) {
 	protected final void setApplicationContext(ApplicationContext context) {
 		this.context = context;
 		this.context = context;
 	}
 	}
@@ -85,7 +95,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * instances.
 	 * instances.
 	 * @param method the {@link HttpMethod} to use for any {@link HttpMethod}.
 	 * @param method the {@link HttpMethod} to use for any {@link HttpMethod}.
 	 * @return the object that is chained after creating the {@link RequestMatcher}
 	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 * @deprecated use {@link #requestMatchers(HttpMethod)} instead
 	 */
 	 */
+	@Deprecated
 	public C antMatchers(HttpMethod method) {
 	public C antMatchers(HttpMethod method) {
 		return antMatchers(method, "/**");
 		return antMatchers(method, "/**");
 	}
 	}
@@ -99,7 +111,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * @param antPatterns the ant patterns to create. If {@code null} or empty, then
 	 * @param antPatterns the ant patterns to create. If {@code null} or empty, then
 	 * matches on nothing.
 	 * matches on nothing.
 	 * @return the object that is chained after creating the {@link RequestMatcher}
 	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 * @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
 	 */
 	 */
+	@Deprecated
 	public C antMatchers(HttpMethod method, String... antPatterns) {
 	public C antMatchers(HttpMethod method, String... antPatterns) {
 		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
 		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
 		return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
 		return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
@@ -112,7 +126,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * @param antPatterns the ant patterns to create
 	 * @param antPatterns the ant patterns to create
 	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} from
 	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} from
 	 * @return the object that is chained after creating the {@link RequestMatcher}
 	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 * @deprecated use {@link #requestMatchers(String...)} instead
 	 */
 	 */
+	@Deprecated
 	public C antMatchers(String... antPatterns) {
 	public C antMatchers(String... antPatterns) {
 		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
 		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
 		return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
 		return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
@@ -132,7 +148,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
 	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
 	 * Spring MVC
 	 * Spring MVC
 	 * @return the object that is chained after creating the {@link RequestMatcher}.
 	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 * @deprecated use {@link #requestMatchers(String...)} instead
 	 */
 	 */
+	@Deprecated
 	public abstract C mvcMatchers(String... mvcPatterns);
 	public abstract C mvcMatchers(String... mvcPatterns);
 
 
 	/**
 	/**
@@ -150,7 +168,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
 	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
 	 * Spring MVC
 	 * Spring MVC
 	 * @return the object that is chained after creating the {@link RequestMatcher}.
 	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 * @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
 	 */
 	 */
+	@Deprecated
 	public abstract C mvcMatchers(HttpMethod method, String... mvcPatterns);
 	public abstract C mvcMatchers(HttpMethod method, String... mvcPatterns);
 
 
 	/**
 	/**
@@ -190,7 +210,10 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * @param regexPatterns the regular expressions to create
 	 * @param regexPatterns the regular expressions to create
 	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
 	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
 	 * @return the object that is chained after creating the {@link RequestMatcher}
 	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 * @deprecated use {@link #requestMatchers(RequestMatcher...)} with a
+	 * {@link RegexRequestMatcher} instead
 	 */
 	 */
+	@Deprecated
 	public C regexMatchers(HttpMethod method, String... regexPatterns) {
 	public C regexMatchers(HttpMethod method, String... regexPatterns) {
 		Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
 		Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
 		return chainRequestMatchers(RequestMatchers.regexMatchers(method, regexPatterns));
 		return chainRequestMatchers(RequestMatchers.regexMatchers(method, regexPatterns));
@@ -203,7 +226,10 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 	 * @param regexPatterns the regular expressions to create
 	 * @param regexPatterns the regular expressions to create
 	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
 	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
 	 * @return the object that is chained after creating the {@link RequestMatcher}
 	 * @return the object that is chained after creating the {@link RequestMatcher}
+	 * @deprecated use {@link #requestMatchers(RequestMatcher...)} with a
+	 * {@link RegexRequestMatcher} instead
 	 */
 	 */
+	@Deprecated
 	public C regexMatchers(String... regexPatterns) {
 	public C regexMatchers(String... regexPatterns) {
 		Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
 		Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
 		return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
 		return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
@@ -250,6 +276,81 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 		return chainRequestMatchers(Arrays.asList(requestMatchers));
 		return chainRequestMatchers(Arrays.asList(requestMatchers));
 	}
 	}
 
 
+	/**
+	 * <p>
+	 * If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
+	 * {@link MvcRequestMatcher} that also specifies a specific {@link HttpMethod} to
+	 * match on. This matcher will use the same rules that Spring MVC uses for matching.
+	 * For example, often times a mapping of the path "/path" will match on "/path",
+	 * "/path/", "/path.html", etc. If the {@link HandlerMappingIntrospector} is not
+	 * available, maps to an {@link AntPathRequestMatcher}.
+	 * </p>
+	 * <p>
+	 * If a specific {@link RequestMatcher} must be specified, use
+	 * {@link #requestMatchers(RequestMatcher...)} instead
+	 * </p>
+	 * @param method the {@link HttpMethod} to use or {@code null} for any
+	 * {@link HttpMethod}.
+	 * @param patterns the patterns to match on. The rules for matching are defined by
+	 * Spring MVC if {@link MvcRequestMatcher} is used
+	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 * @since 5.8
+	 */
+	public C requestMatchers(HttpMethod method, String... patterns) {
+		List<RequestMatcher> matchers = new ArrayList<>();
+		if (mvcPresent) {
+			matchers.addAll(createMvcMatchers(method, patterns));
+		}
+		else {
+			matchers.addAll(RequestMatchers.antMatchers(method, patterns));
+		}
+		return requestMatchers(matchers.toArray(new RequestMatcher[0]));
+	}
+
+	/**
+	 * <p>
+	 * If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
+	 * {@link MvcRequestMatcher} that does not care which {@link HttpMethod} is used. This
+	 * matcher will use the same rules that Spring MVC uses for matching. For example,
+	 * often times a mapping of the path "/path" will match on "/path", "/path/",
+	 * "/path.html", etc. If the {@link HandlerMappingIntrospector} is not available, maps
+	 * to an {@link AntPathRequestMatcher}.
+	 * </p>
+	 * <p>
+	 * If a specific {@link RequestMatcher} must be specified, use
+	 * {@link #requestMatchers(RequestMatcher...)} instead
+	 * </p>
+	 * @param patterns the patterns to match on. The rules for matching are defined by
+	 * Spring MVC if {@link MvcRequestMatcher} is used
+	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 * @since 5.8
+	 */
+	public C requestMatchers(String... patterns) {
+		return requestMatchers(null, patterns);
+	}
+
+	/**
+	 * <p>
+	 * If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
+	 * {@link MvcRequestMatcher} that matches on a specific {@link HttpMethod}. This
+	 * matcher will use the same rules that Spring MVC uses for matching. For example,
+	 * often times a mapping of the path "/path" will match on "/path", "/path/",
+	 * "/path.html", etc. If the {@link HandlerMappingIntrospector} is not available, maps
+	 * to an {@link AntPathRequestMatcher}.
+	 * </p>
+	 * <p>
+	 * If a specific {@link RequestMatcher} must be specified, use
+	 * {@link #requestMatchers(RequestMatcher...)} instead
+	 * </p>
+	 * @param method the {@link HttpMethod} to use or {@code null} for any
+	 * {@link HttpMethod}.
+	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 * @since 5.8
+	 */
+	public C requestMatchers(HttpMethod method) {
+		return requestMatchers(method, "/**");
+	}
+
 	/**
 	/**
 	 * Subclasses should implement this method for returning the object that is chained to
 	 * Subclasses should implement this method for returning the object that is chained to
 	 * the creation of the {@link RequestMatcher} instances.
 	 * the creation of the {@link RequestMatcher} instances.

+ 336 - 3
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -28,6 +28,7 @@ import jakarta.servlet.ServletRequest;
 import jakarta.servlet.ServletResponse;
 import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
 
 
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.OrderComparator;
 import org.springframework.core.OrderComparator;
 import org.springframework.core.Ordered;
 import org.springframework.core.Ordered;
@@ -91,6 +92,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.filter.CorsFilter;
 import org.springframework.web.filter.CorsFilter;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@@ -116,7 +118,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
  *
  *
  * 	&#064;Bean
  * 	&#064;Bean
  * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- * 		http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin();
+ * 		http.authorizeHttpRequests().requestMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin();
  * 		return http.build();
  * 		return http.build();
  * 	}
  * 	}
  *
  *
@@ -140,6 +142,12 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
 public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
 		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
 		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
 
 
+	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
+
+	private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
+
+	private static final boolean mvcPresent;
+
 	private final RequestMatcherConfigurer requestMatcherConfigurer;
 	private final RequestMatcherConfigurer requestMatcherConfigurer;
 
 
 	private List<OrderedFilter> filters = new ArrayList<>();
 	private List<OrderedFilter> filters = new ArrayList<>();
@@ -150,6 +158,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 
 
 	private AuthenticationManager authenticationManager;
 	private AuthenticationManager authenticationManager;
 
 
+	static {
+		mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, HttpSecurity.class.getClassLoader());
+	}
+
 	/**
 	/**
 	 * Creates a new instance
 	 * Creates a new instance
 	 * @param objectPostProcessor the {@link ObjectPostProcessor} that should be used
 	 * @param objectPostProcessor the {@link ObjectPostProcessor} that should be used
@@ -3193,7 +3205,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * }
 	 * }
 	 * </pre>
 	 * </pre>
 	 * @return the {@link RequestMatcherConfigurer} for further customizations
 	 * @return the {@link RequestMatcherConfigurer} for further customizations
+	 * @deprecated use {@link #securityMatchers()} instead
 	 */
 	 */
+	@Deprecated
 	public RequestMatcherConfigurer requestMatchers() {
 	public RequestMatcherConfigurer requestMatchers() {
 		return this.requestMatcherConfigurer;
 		return this.requestMatcherConfigurer;
 	}
 	}
@@ -3325,7 +3339,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * @param requestMatcherCustomizer the {@link Customizer} to provide more options for
 	 * @param requestMatcherCustomizer the {@link Customizer} to provide more options for
 	 * the {@link RequestMatcherConfigurer}
 	 * the {@link RequestMatcherConfigurer}
 	 * @return the {@link HttpSecurity} for further customizations
 	 * @return the {@link HttpSecurity} for further customizations
+	 * @deprecated use {@link #securityMatchers(Customizer)} instead
 	 */
 	 */
+	@Deprecated
 	public HttpSecurity requestMatchers(Customizer<RequestMatcherConfigurer> requestMatcherCustomizer) {
 	public HttpSecurity requestMatchers(Customizer<RequestMatcherConfigurer> requestMatcherCustomizer) {
 		requestMatcherCustomizer.customize(this.requestMatcherConfigurer);
 		requestMatcherCustomizer.customize(this.requestMatcherConfigurer);
 		return HttpSecurity.this;
 		return HttpSecurity.this;
@@ -3345,15 +3361,318 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * @param requestMatcher the {@link RequestMatcher} to use (i.e. new
 	 * @param requestMatcher the {@link RequestMatcher} to use (i.e. new
 	 * AntPathRequestMatcher("/admin/**","GET") )
 	 * AntPathRequestMatcher("/admin/**","GET") )
 	 * @return the {@link HttpSecurity} for further customizations
 	 * @return the {@link HttpSecurity} for further customizations
+	 * @deprecated use {@link #securityMatcher(RequestMatcher)} instead
 	 * @see #requestMatchers()
 	 * @see #requestMatchers()
 	 * @see #antMatcher(String)
 	 * @see #antMatcher(String)
 	 * @see #regexMatcher(String)
 	 * @see #regexMatcher(String)
 	 */
 	 */
+	@Deprecated
 	public HttpSecurity requestMatcher(RequestMatcher requestMatcher) {
 	public HttpSecurity requestMatcher(RequestMatcher requestMatcher) {
 		this.requestMatcher = requestMatcher;
 		this.requestMatcher = requestMatcher;
 		return this;
 		return this;
 	}
 	}
 
 
+	/**
+	 * Allows specifying which {@link HttpServletRequest} instances this
+	 * {@link HttpSecurity} will be invoked on. This method allows for easily invoking the
+	 * {@link HttpSecurity} for multiple different {@link RequestMatcher} instances. If
+	 * only a single {@link RequestMatcher} is necessary consider using
+	 * {@link #securityMatcher(String)}, or {@link #securityMatcher(RequestMatcher)}.
+	 *
+	 * <p>
+	 * Invoking {@link #securityMatchers()} will not override previous invocations of
+	 * {@link #securityMatchers()}}, {@link #securityMatchers(Customizer)}
+	 * {@link #securityMatcher(String)} and {@link #securityMatcher(RequestMatcher)}
+	 * </p>
+	 *
+	 * <h3>Example Configurations</h3>
+	 *
+	 * The following configuration enables the {@link HttpSecurity} for URLs that begin
+	 * with "/api/" or "/oauth/".
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig {
+	 *
+	 * 	&#064;Bean
+	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityMatchers((matchers) -&gt; matchers
+	 * 				.requestMatchers(&quot;/api/**&quot;, &quot;/oauth/**&quot;)
+	 * 			)
+	 * 			.authorizeHttpRequests((authorize) -&gt; authorize
+	 * 				anyRequest().hasRole(&quot;USER&quot;)
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 		return http.build();
+	 * 	}
+	 *
+	 * 	&#064;Bean
+	 * 	public UserDetailsService userDetailsService() {
+	 * 		UserDetails user = User.withDefaultPasswordEncoder()
+	 * 			.username(&quot;user&quot;)
+	 * 			.password(&quot;password&quot;)
+	 * 			.roles(&quot;USER&quot;)
+	 * 			.build();
+	 * 		return new InMemoryUserDetailsManager(user);
+	 * 	}
+	 * }
+	 * </pre>
+	 *
+	 * The configuration below is the same as the previous configuration.
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig {
+	 *
+	 * 	&#064;Bean
+	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityMatchers((matchers) -&gt; matchers
+	 * 				.requestMatchers(&quot;/api/**&quot;)
+	 * 				.requestMatchers(&quot;/oauth/**&quot;)
+	 * 			)
+	 * 			.authorizeHttpRequests((authorize) -&gt; authorize
+	 * 				anyRequest().hasRole(&quot;USER&quot;)
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 		return http.build();
+	 * 	}
+	 *
+	 * 	&#064;Bean
+	 * 	public UserDetailsService userDetailsService() {
+	 * 		UserDetails user = User.withDefaultPasswordEncoder()
+	 * 			.username(&quot;user&quot;)
+	 * 			.password(&quot;password&quot;)
+	 * 			.roles(&quot;USER&quot;)
+	 * 			.build();
+	 * 		return new InMemoryUserDetailsManager(user);
+	 * 	}
+	 * }
+	 * </pre>
+	 *
+	 * The configuration below is also the same as the above configuration.
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig {
+	 *
+	 * 	&#064;Bean
+	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityMatchers((matchers) -&gt; matchers
+	 * 				.requestMatchers(&quot;/api/**&quot;)
+	 * 			)
+	 *			.securityMatchers((matchers) -&gt; matchers
+	 *				.requestMatchers(&quot;/oauth/**&quot;)
+	 * 			)
+	 * 			.authorizeHttpRequests((authorize) -&gt; authorize
+	 * 				anyRequest().hasRole(&quot;USER&quot;)
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 		return http.build();
+	 * 	}
+	 *
+	 * 	&#064;Bean
+	 * 	public UserDetailsService userDetailsService() {
+	 * 		UserDetails user = User.withDefaultPasswordEncoder()
+	 * 			.username(&quot;user&quot;)
+	 * 			.password(&quot;password&quot;)
+	 * 			.roles(&quot;USER&quot;)
+	 * 			.build();
+	 * 		return new InMemoryUserDetailsManager(user);
+	 * 	}
+	 * }
+	 * </pre>
+	 * @return the {@link RequestMatcherConfigurer} for further customizations
+	 */
+	public RequestMatcherConfigurer securityMatchers() {
+		return this.requestMatcherConfigurer;
+	}
+
+	/**
+	 * Allows specifying which {@link HttpServletRequest} instances this
+	 * {@link HttpSecurity} will be invoked on. This method allows for easily invoking the
+	 * {@link HttpSecurity} for multiple different {@link RequestMatcher} instances. If
+	 * only a single {@link RequestMatcher} is necessary consider using
+	 * {@link #securityMatcher(String)}, or {@link #securityMatcher(RequestMatcher)}.
+	 *
+	 * <p>
+	 * Invoking {@link #securityMatchers(Customizer)} will not override previous
+	 * invocations of {@link #securityMatchers()}}, {@link #securityMatchers(Customizer)}
+	 * {@link #securityMatcher(String)} and {@link #securityMatcher(RequestMatcher)}
+	 * </p>
+	 *
+	 * <h3>Example Configurations</h3>
+	 *
+	 * The following configuration enables the {@link HttpSecurity} for URLs that begin
+	 * with "/api/" or "/oauth/".
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig {
+	 *
+	 * 	&#064;Bean
+	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityMatchers((matchers) -&gt; matchers
+	 * 				.requestMatchers(&quot;/api/**&quot;, &quot;/oauth/**&quot;)
+	 * 			)
+	 * 			.authorizeHttpRequests((authorize) -&gt; authorize
+	 * 				.anyRequest().hasRole(&quot;USER&quot;)
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 		return http.build();
+	 * 	}
+	 *
+	 * 	&#064;Bean
+	 * 	public UserDetailsService userDetailsService() {
+	 * 		UserDetails user = User.withDefaultPasswordEncoder()
+	 * 			.username(&quot;user&quot;)
+	 * 			.password(&quot;password&quot;)
+	 * 			.roles(&quot;USER&quot;)
+	 * 			.build();
+	 * 		return new InMemoryUserDetailsManager(user);
+	 * 	}
+	 * }
+	 * </pre>
+	 *
+	 * The configuration below is the same as the previous configuration.
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig {
+	 *
+	 * 	&#064;Bean
+	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityMatchers((matchers) -&gt; matchers
+	 * 				.requestMatchers(&quot;/api/**&quot;)
+	 * 				.requestMatchers(&quot;/oauth/**&quot;)
+	 * 			)
+	 * 			.authorizeHttpRequests((authorize) -&gt; authorize
+	 * 				.anyRequest().hasRole(&quot;USER&quot;)
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 		return http.build();
+	 * 	}
+	 *
+	 * 	&#064;Bean
+	 * 	public UserDetailsService userDetailsService() {
+	 * 		UserDetails user = User.withDefaultPasswordEncoder()
+	 * 			.username(&quot;user&quot;)
+	 * 			.password(&quot;password&quot;)
+	 * 			.roles(&quot;USER&quot;)
+	 * 			.build();
+	 * 		return new InMemoryUserDetailsManager(user);
+	 * 	}
+	 * }
+	 * </pre>
+	 *
+	 * The configuration below is also the same as the above configuration.
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig {
+	 *
+	 * 	&#064;Bean
+	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.securityMatchers((matchers) -&gt; matchers
+	 * 				.requestMatchers(&quot;/api/**&quot;)
+	 * 			)
+	 *			.securityMatchers((matchers) -&gt; matchers
+	 *				.requestMatchers(&quot;/oauth/**&quot;)
+	 * 			)
+	 * 			.authorizeHttpRequests((authorize) -&gt; authorize
+	 * 				.anyRequest().hasRole(&quot;USER&quot;)
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 		return http.build();
+	 * 	}
+	 *
+	 * 	&#064;Bean
+	 * 	public UserDetailsService userDetailsService() {
+	 * 		UserDetails user = User.withDefaultPasswordEncoder()
+	 * 			.username(&quot;user&quot;)
+	 * 			.password(&quot;password&quot;)
+	 * 			.roles(&quot;USER&quot;)
+	 * 			.build();
+	 * 		return new InMemoryUserDetailsManager(user);
+	 * 	}
+	 * }
+	 * </pre>
+	 * @param requestMatcherCustomizer the {@link Customizer} to provide more options for
+	 * the {@link RequestMatcherConfigurer}
+	 * @return the {@link HttpSecurity} for further customizations
+	 */
+	public HttpSecurity securityMatchers(Customizer<RequestMatcherConfigurer> requestMatcherCustomizer) {
+		requestMatcherCustomizer.customize(this.requestMatcherConfigurer);
+		return HttpSecurity.this;
+	}
+
+	/**
+	 * Allows configuring the {@link HttpSecurity} to only be invoked when matching the
+	 * provided {@link RequestMatcher}. If more advanced configuration is necessary,
+	 * consider using {@link #securityMatchers(Customizer)} ()}.
+	 *
+	 * <p>
+	 * Invoking {@link #securityMatcher(RequestMatcher)} will override previous
+	 * invocations of {@link #requestMatchers()}, {@link #mvcMatcher(String)},
+	 * {@link #antMatcher(String)}, {@link #regexMatcher(String)},
+	 * {@link #requestMatcher(RequestMatcher)}, {@link #securityMatchers(Customizer)},
+	 * {@link #securityMatchers()} and {@link #securityMatcher(String)}
+	 * </p>
+	 * @param requestMatcher the {@link RequestMatcher} to use (i.e. new
+	 * AntPathRequestMatcher("/admin/**","GET") )
+	 * @return the {@link HttpSecurity} for further customizations
+	 * @see #securityMatcher(String)
+	 */
+	public HttpSecurity securityMatcher(RequestMatcher requestMatcher) {
+		this.requestMatcher = requestMatcher;
+		return this;
+	}
+
+	/**
+	 * Allows configuring the {@link HttpSecurity} to only be invoked when matching the
+	 * provided pattern. This method creates a {@link MvcRequestMatcher} if Spring MVC is
+	 * in the classpath or creates an {@link AntPathRequestMatcher} if not. If more
+	 * advanced configuration is necessary, consider using
+	 * {@link #securityMatchers(Customizer)} or {@link #securityMatcher(RequestMatcher)}.
+	 *
+	 * <p>
+	 * Invoking {@link #securityMatcher(String)} will override previous invocations of
+	 * {@link #mvcMatcher(String)}}, {@link #requestMatchers()},
+	 * {@link #antMatcher(String)}, {@link #regexMatcher(String)}, and
+	 * {@link #requestMatcher(RequestMatcher)}.
+	 * </p>
+	 * @param pattern the pattern to match on (i.e. "/admin/**")
+	 * @return the {@link HttpSecurity} for further customizations
+	 * @see AntPathRequestMatcher
+	 * @see MvcRequestMatcher
+	 */
+	public HttpSecurity securityMatcher(String pattern) {
+		if (!mvcPresent) {
+			this.requestMatcher = new AntPathRequestMatcher(pattern);
+			return this;
+		}
+		if (!getContext().containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
+			throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME
+					+ " of type " + HandlerMappingIntrospector.class.getName()
+					+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
+		}
+		HandlerMappingIntrospector introspector = getContext().getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
+				HandlerMappingIntrospector.class);
+		this.requestMatcher = new MvcRequestMatcher(introspector, pattern);
+		return this;
+	}
+
 	/**
 	/**
 	 * Allows configuring the {@link HttpSecurity} to only be invoked when matching the
 	 * Allows configuring the {@link HttpSecurity} to only be invoked when matching the
 	 * provided ant pattern. If more advanced configuration is necessary, consider using
 	 * provided ant pattern. If more advanced configuration is necessary, consider using
@@ -3367,8 +3686,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * </p>
 	 * </p>
 	 * @param antPattern the Ant Pattern to match on (i.e. "/admin/**")
 	 * @param antPattern the Ant Pattern to match on (i.e. "/admin/**")
 	 * @return the {@link HttpSecurity} for further customizations
 	 * @return the {@link HttpSecurity} for further customizations
+	 * @deprecated use {@link #securityMatcher(String)} instead
 	 * @see AntPathRequestMatcher
 	 * @see AntPathRequestMatcher
 	 */
 	 */
+	@Deprecated
 	public HttpSecurity antMatcher(String antPattern) {
 	public HttpSecurity antMatcher(String antPattern) {
 		return requestMatcher(new AntPathRequestMatcher(antPattern));
 		return requestMatcher(new AntPathRequestMatcher(antPattern));
 	}
 	}
@@ -3386,8 +3707,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * </p>
 	 * </p>
 	 * @param mvcPattern the Spring MVC Pattern to match on (i.e. "/admin/**")
 	 * @param mvcPattern the Spring MVC Pattern to match on (i.e. "/admin/**")
 	 * @return the {@link HttpSecurity} for further customizations
 	 * @return the {@link HttpSecurity} for further customizations
+	 * @deprecated use {@link #securityMatcher(String)} instead
 	 * @see MvcRequestMatcher
 	 * @see MvcRequestMatcher
 	 */
 	 */
+	@Deprecated
 	public HttpSecurity mvcMatcher(String mvcPattern) {
 	public HttpSecurity mvcMatcher(String mvcPattern) {
 		HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
 		HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
 		introspector.setApplicationContext(getContext());
 		introspector.setApplicationContext(getContext());
@@ -3408,8 +3731,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * </p>
 	 * </p>
 	 * @param pattern the Regular Expression to match on (i.e. "/admin/.+")
 	 * @param pattern the Regular Expression to match on (i.e. "/admin/.+")
 	 * @return the {@link HttpSecurity} for further customizations
 	 * @return the {@link HttpSecurity} for further customizations
-	 * @see RegexRequestMatcher
+	 * @deprecated use {@link #securityMatcher(RequestMatcher)} with a
+	 * {@link RegexRequestMatcher} instead
 	 */
 	 */
+	@Deprecated
 	public HttpSecurity regexMatcher(String pattern) {
 	public HttpSecurity regexMatcher(String pattern) {
 		return requestMatcher(new RegexRequestMatcher(pattern, null));
 		return requestMatcher(new RegexRequestMatcher(pattern, null));
 	}
 	}
@@ -3480,14 +3805,22 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 			setApplicationContext(context);
 			setApplicationContext(context);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
 		public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
 			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
 			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
 			setMatchers(mvcMatchers);
 			setMatchers(mvcMatchers);
 			return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
 			return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersRequestMatcherConfigurer mvcMatchers(String... patterns) {
 		public MvcMatchersRequestMatcherConfigurer mvcMatchers(String... patterns) {
 			return mvcMatchers(null, patterns);
 			return mvcMatchers(null, patterns);
 		}
 		}
@@ -3500,7 +3833,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 
 
 		private void setMatchers(List<? extends RequestMatcher> requestMatchers) {
 		private void setMatchers(List<? extends RequestMatcher> requestMatchers) {
 			this.matchers.addAll(requestMatchers);
 			this.matchers.addAll(requestMatchers);
-			requestMatcher(new OrRequestMatcher(this.matchers));
+			securityMatcher(new OrRequestMatcher(this.matchers));
 		}
 		}
 
 
 		/**
 		/**

+ 14 - 4
config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

@@ -135,7 +135,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 	 * <pre>
 	 * <pre>
 	 * webSecurityBuilder.ignoring()
 	 * webSecurityBuilder.ignoring()
 	 * // ignore all URLs that start with /resources/ or /static/
 	 * // ignore all URLs that start with /resources/ or /static/
-	 * 		.antMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
+	 * 		.requestMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
 	 * </pre>
 	 * </pre>
 	 *
 	 *
 	 * Alternatively this will accomplish the same result:
 	 * Alternatively this will accomplish the same result:
@@ -143,7 +143,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 	 * <pre>
 	 * <pre>
 	 * webSecurityBuilder.ignoring()
 	 * webSecurityBuilder.ignoring()
 	 * // ignore all URLs that start with /resources/ or /static/
 	 * // ignore all URLs that start with /resources/ or /static/
-	 * 		.antMatchers(&quot;/resources/**&quot;).antMatchers(&quot;/static/**&quot;);
+	 * 		.requestMatchers(&quot;/resources/**&quot;).requestMatchers(&quot;/static/**&quot;);
 	 * </pre>
 	 * </pre>
 	 *
 	 *
 	 * Multiple invocations of ignoring() are also additive, so the following is also
 	 * Multiple invocations of ignoring() are also additive, so the following is also
@@ -152,10 +152,10 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 	 * <pre>
 	 * <pre>
 	 * webSecurityBuilder.ignoring()
 	 * webSecurityBuilder.ignoring()
 	 * // ignore all URLs that start with /resources/
 	 * // ignore all URLs that start with /resources/
-	 * 		.antMatchers(&quot;/resources/**&quot;);
+	 * 		.requestMatchers(&quot;/resources/**&quot;);
 	 * webSecurityBuilder.ignoring()
 	 * webSecurityBuilder.ignoring()
 	 * // ignore all URLs that start with /static/
 	 * // ignore all URLs that start with /static/
-	 * 		.antMatchers(&quot;/static/**&quot;);
+	 * 		.requestMatchers(&quot;/static/**&quot;);
 	 * // now both URLs that start with /resources/ and /static/ will be ignored
 	 * // now both URLs that start with /resources/ and /static/ will be ignored
 	 * </pre>
 	 * </pre>
 	 * @return the {@link IgnoredRequestConfigurer} to use for registering request that
 	 * @return the {@link IgnoredRequestConfigurer} to use for registering request that
@@ -380,7 +380,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 	 * {@link MvcRequestMatcher#setMethod(HttpMethod)}
 	 * {@link MvcRequestMatcher#setMethod(HttpMethod)}
 	 *
 	 *
 	 * @author Rob Winch
 	 * @author Rob Winch
+	 * @deprecated use {@link MvcRequestMatcher.Builder} instead
 	 */
 	 */
+	@Deprecated
 	public final class MvcMatchersIgnoredRequestConfigurer extends IgnoredRequestConfigurer {
 	public final class MvcMatchersIgnoredRequestConfigurer extends IgnoredRequestConfigurer {
 
 
 		private final List<MvcRequestMatcher> mvcMatchers;
 		private final List<MvcRequestMatcher> mvcMatchers;
@@ -412,14 +414,22 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 			setApplicationContext(context);
 			setApplicationContext(context);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
 		public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
 			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
 			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
 			WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
 			WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
 			return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(), mvcMatchers);
 			return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(), mvcMatchers);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) {
 		public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) {
 			return mvcMatchers(null, mvcPatterns);
 			return mvcMatchers(null, mvcPatterns);
 		}
 		}

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

@@ -41,12 +41,12 @@ import org.springframework.security.web.SecurityFilterChain;
  * 	public WebSecurityCustomizer webSecurityCustomizer() {
  * 	public WebSecurityCustomizer webSecurityCustomizer() {
  * 		return (web) -> web.ignoring()
  * 		return (web) -> web.ignoring()
  * 		// Spring Security should completely ignore URLs starting with /resources/
  * 		// Spring Security should completely ignore URLs starting with /resources/
- * 				.antMatchers(&quot;/resources/**&quot;);
+ * 				.requestMatchers(&quot;/resources/**&quot;);
  * 	}
  * 	}
  *
  *
  * 	&#064;Bean
  * 	&#064;Bean
  * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- * 		http.authorizeRequests().antMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
+ * 		http.authorizeRequests().requestMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
  * 				.hasRole(&quot;USER&quot;).and()
  * 				.hasRole(&quot;USER&quot;).and()
  * 				// Possibly more configuration ...
  * 				// Possibly more configuration ...
  * 				.formLogin() // enable form based log in
  * 				.formLogin() // enable form based log in

+ 10 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -146,12 +146,20 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			return postProcess(this.managerBuilder.build());
 			return postProcess(this.managerBuilder.build());
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersAuthorizedUrl mvcMatchers(String... mvcPatterns) {
 		public MvcMatchersAuthorizedUrl mvcMatchers(String... mvcPatterns) {
 			return mvcMatchers(null, mvcPatterns);
 			return mvcMatchers(null, mvcPatterns);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns) {
 		public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns) {
 			return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
 			return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
 		}
 		}
@@ -202,7 +210,9 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 	 * configuring the {@link MvcRequestMatcher#setServletPath(String)}.
 	 * configuring the {@link MvcRequestMatcher#setServletPath(String)}.
 	 *
 	 *
 	 * @author Evgeniy Cheban
 	 * @author Evgeniy Cheban
+	 * @deprecated use {@link MvcRequestMatcher.Builder} instead
 	 */
 	 */
+	@Deprecated
 	public final class MvcMatchersAuthorizedUrl extends AuthorizedUrl {
 	public final class MvcMatchersAuthorizedUrl extends AuthorizedUrl {
 
 
 		private MvcMatchersAuthorizedUrl(List<MvcRequestMatcher> matchers) {
 		private MvcMatchersAuthorizedUrl(List<MvcRequestMatcher> matchers) {

+ 40 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java

@@ -163,7 +163,10 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
 	 * </pre>
 	 * </pre>
 	 *
 	 *
 	 * @since 4.0
 	 * @since 4.0
+	 * @deprecated use {@link #ignoringRequestMatchers(RequestMatcher...)} with an
+	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} instead
 	 */
 	 */
+	@Deprecated
 	public CsrfConfigurer<H> ignoringAntMatchers(String... antPatterns) {
 	public CsrfConfigurer<H> ignoringAntMatchers(String... antPatterns) {
 		return new IgnoreCsrfProtectionRegistry(this.context).antMatchers(antPatterns).and();
 		return new IgnoreCsrfProtectionRegistry(this.context).antMatchers(antPatterns).and();
 	}
 	}
@@ -197,6 +200,35 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
 		return new IgnoreCsrfProtectionRegistry(this.context).requestMatchers(requestMatchers).and();
 		return new IgnoreCsrfProtectionRegistry(this.context).requestMatchers(requestMatchers).and();
 	}
 	}
 
 
+	/**
+	 * <p>
+	 * Allows specifying {@link HttpServletRequest} that should not use CSRF Protection
+	 * even if they match the {@link #requireCsrfProtectionMatcher(RequestMatcher)}.
+	 * </p>
+	 *
+	 * <p>
+	 * For example, the following configuration will ensure CSRF protection ignores:
+	 * </p>
+	 * <ul>
+	 * <li>Any GET, HEAD, TRACE, OPTIONS (this is the default)</li>
+	 * <li>We also explicitly state to ignore any request that starts with "/sockjs/"</li>
+	 * </ul>
+	 *
+	 * <pre>
+	 * http
+	 *     .csrf()
+	 *         .ignoringRequestMatchers("/sockjs/**")
+	 *         .and()
+	 *     ...
+	 * </pre>
+	 *
+	 * @since 5.8
+	 * @see AbstractRequestMatcherRegistry#requestMatchers(String...)
+	 */
+	public CsrfConfigurer<H> ignoringRequestMatchers(String... patterns) {
+		return new IgnoreCsrfProtectionRegistry(this.context).requestMatchers(patterns).and();
+	}
+
 	/**
 	/**
 	 * <p>
 	 * <p>
 	 * Specify the {@link SessionAuthenticationStrategy} to use. The default is a
 	 * Specify the {@link SessionAuthenticationStrategy} to use. The default is a
@@ -350,14 +382,22 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
 			setApplicationContext(context);
 			setApplicationContext(context);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(HttpMethod, String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(HttpMethod method, String... mvcPatterns) {
 		public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(HttpMethod method, String... mvcPatterns) {
 			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
 			List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
 			CsrfConfigurer.this.ignoredCsrfProtectionMatchers.addAll(mvcMatchers);
 			CsrfConfigurer.this.ignoredCsrfProtectionMatchers.addAll(mvcMatchers);
 			return new MvcMatchersIgnoreCsrfProtectionRegistry(getApplicationContext(), mvcMatchers);
 			return new MvcMatchersIgnoreCsrfProtectionRegistry(getApplicationContext(), mvcMatchers);
 		}
 		}
 
 
+		/**
+		 * @deprecated use {@link #requestMatchers(String...)} instead
+		 */
 		@Override
 		@Override
+		@Deprecated
 		public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(String... mvcPatterns) {
 		public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(String... mvcPatterns) {
 			return mvcMatchers(null, mvcPatterns);
 			return mvcMatchers(null, mvcPatterns);
 		}
 		}

+ 13 - 1
config/src/main/kotlin/org/springframework/security/config/annotation/web/CsrfDsl.kt

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ class CsrfDsl {
 
 
     private var ignoringAntMatchers: Array<out String>? = null
     private var ignoringAntMatchers: Array<out String>? = null
     private var ignoringRequestMatchers: Array<out RequestMatcher>? = null
     private var ignoringRequestMatchers: Array<out RequestMatcher>? = null
+    private var ignoringRequestMatchersPatterns: Array<out String>? = null
     private var disabled = false
     private var disabled = false
 
 
     /**
     /**
@@ -66,6 +67,16 @@ class CsrfDsl {
         ignoringRequestMatchers = requestMatchers
         ignoringRequestMatchers = requestMatchers
     }
     }
 
 
+    /**
+     * Allows specifying [HttpServletRequest]s that should not use CSRF Protection
+     * even if they match the [requireCsrfProtectionMatcher].
+     *
+     * @param patterns the patterns that should not use CSRF protection
+     */
+    fun ignoringRequestMatchers(vararg patterns: String) {
+        ignoringRequestMatchersPatterns = patterns
+    }
+
     /**
     /**
      * Disable CSRF protection
      * Disable CSRF protection
      */
      */
@@ -80,6 +91,7 @@ class CsrfDsl {
             sessionAuthenticationStrategy?.also { csrf.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
             sessionAuthenticationStrategy?.also { csrf.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
             ignoringAntMatchers?.also { csrf.ignoringAntMatchers(*ignoringAntMatchers!!) }
             ignoringAntMatchers?.also { csrf.ignoringAntMatchers(*ignoringAntMatchers!!) }
             ignoringRequestMatchers?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchers!!) }
             ignoringRequestMatchers?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchers!!) }
+            ignoringRequestMatchersPatterns?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchersPatterns!!) }
             if (disabled) {
             if (disabled) {
                 csrf.disable()
                 csrf.disable()
             }
             }

+ 105 - 1
config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -16,19 +16,28 @@
 
 
 package org.springframework.security.config.annotation.web;
 package org.springframework.security.config.annotation.web;
 
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.List;
 import java.util.List;
 
 
 import jakarta.servlet.DispatcherType;
 import jakarta.servlet.DispatcherType;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 
 
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
 
 
 /**
 /**
  * Tests for {@link AbstractRequestMatcherRegistry}.
  * Tests for {@link AbstractRequestMatcherRegistry}.
@@ -37,11 +46,21 @@ import static org.assertj.core.api.Assertions.assertThat;
  */
  */
 public class AbstractRequestMatcherRegistryTests {
 public class AbstractRequestMatcherRegistryTests {
 
 
+	private static final ObjectPostProcessor<Object> NO_OP_OBJECT_POST_PROCESSOR = new ObjectPostProcessor<Object>() {
+		@Override
+		public <O> O postProcess(O object) {
+			return object;
+		}
+	};
+
 	private TestRequestMatcherRegistry matcherRegistry;
 	private TestRequestMatcherRegistry matcherRegistry;
 
 
 	@BeforeEach
 	@BeforeEach
 	public void setUp() {
 	public void setUp() {
 		this.matcherRegistry = new TestRequestMatcherRegistry();
 		this.matcherRegistry = new TestRequestMatcherRegistry();
+		ApplicationContext context = mock(ApplicationContext.class);
+		given(context.getBean(ObjectPostProcessor.class)).willReturn(NO_OP_OBJECT_POST_PROCESSOR);
+		this.matcherRegistry.setApplicationContext(context);
 	}
 	}
 
 
 	@Test
 	@Test
@@ -93,6 +112,91 @@ public class AbstractRequestMatcherRegistryTests {
 		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
 		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
 	}
 	}
 
 
+	@Test
+	public void requestMatchersWhenPatternAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
+		mockMvcPresentClasspath(true);
+		mockMvcIntrospector(true);
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
+	}
+
+	@Test
+	public void requestMatchersWhenHttpMethodAndPatternAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
+		mockMvcPresentClasspath(true);
+		mockMvcIntrospector(true);
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
+	}
+
+	@Test
+	public void requestMatchersWhenHttpMethodAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
+		mockMvcPresentClasspath(true);
+		mockMvcIntrospector(true);
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
+	}
+
+	@Test
+	public void requestMatchersWhenPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() throws Exception {
+		mockMvcPresentClasspath(false);
+		mockMvcIntrospector(false);
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
+	}
+
+	@Test
+	public void requestMatchersWhenHttpMethodAndPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType()
+			throws Exception {
+		mockMvcPresentClasspath(false);
+		mockMvcIntrospector(false);
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
+	}
+
+	@Test
+	public void requestMatchersWhenHttpMethodAndMvcNotPresentThenReturnAntPathMatcherType() throws Exception {
+		mockMvcPresentClasspath(false);
+		mockMvcIntrospector(false);
+		List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
+		assertThat(requestMatchers).isNotEmpty();
+		assertThat(requestMatchers.size()).isEqualTo(1);
+		assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
+	}
+
+	@Test
+	public void requestMatchersWhenMvcPresentInClassPathAndMvcIntrospectorBeanNotAvailableThenException()
+			throws Exception {
+		mockMvcPresentClasspath(true);
+		mockMvcIntrospector(false);
+		assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
+				.isThrownBy(() -> this.matcherRegistry.requestMatchers("/path")).withMessageContaining(
+						"Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext");
+	}
+
+	private void mockMvcIntrospector(boolean isPresent) {
+		ApplicationContext context = this.matcherRegistry.getApplicationContext();
+		given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
+	}
+
+	private void mockMvcPresentClasspath(Object newValue) throws Exception {
+		Field mvcPresentField = AbstractRequestMatcherRegistry.class.getDeclaredField("mvcPresent");
+		mvcPresentField.setAccessible(true);
+		Field modifiersField = Field.class.getDeclaredField("modifiers");
+		modifiersField.setAccessible(true);
+		modifiersField.setInt(mvcPresentField, mvcPresentField.getModifiers() & ~Modifier.FINAL);
+		mvcPresentField.set(null, newValue);
+	}
+
 	private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
 	private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
 
 
 		@Override
 		@Override

+ 54 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java

@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -27,11 +28,13 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 
 
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -80,6 +83,22 @@ public class CsrfConfigurerIgnoringRequestMatchersTests {
 		this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
 		this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
 	}
 	}
 
 
+	@Test
+	public void requestWhenIgnoringRequestMatcherPatternThenIgnores() throws Exception {
+		this.spring.register(IgnoringPathsAndMatchersPatternConfig.class, BasicController.class).autowire();
+		this.mvc.perform(put("/csrf")).andExpect(status().isForbidden());
+		this.mvc.perform(post("/csrf")).andExpect(status().isForbidden());
+		this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
+	}
+
+	@Test
+	public void requestWhenIgnoringRequestMatcherPatternInLambdaThenIgnores() throws Exception {
+		this.spring.register(IgnoringPathsAndMatchersPatternInLambdaConfig.class, BasicController.class).autowire();
+		this.mvc.perform(put("/csrf")).andExpect(status().isForbidden());
+		this.mvc.perform(post("/csrf")).andExpect(status().isForbidden());
+		this.mvc.perform(put("/no-csrf")).andExpect(status().isOk());
+	}
+
 	@Configuration
 	@Configuration
 	@EnableWebSecurity
 	@EnableWebSecurity
 	static class IgnoringRequestMatchers extends WebSecurityConfigurerAdapter {
 	static class IgnoringRequestMatchers extends WebSecurityConfigurerAdapter {
@@ -156,6 +175,41 @@ public class CsrfConfigurerIgnoringRequestMatchersTests {
 
 
 	}
 	}
 
 
+	@Configuration
+	@EnableWebSecurity
+	@EnableWebMvc
+	static class IgnoringPathsAndMatchersPatternConfig {
+
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.csrf()
+					.ignoringRequestMatchers("/no-csrf");
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
+	@Configuration
+	@EnableWebSecurity
+	@EnableWebMvc
+	static class IgnoringPathsAndMatchersPatternInLambdaConfig {
+
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.csrf((csrf) -> csrf
+					.ignoringRequestMatchers("/no-csrf")
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
 	@RestController
 	@RestController
 	public static class BasicController {
 	public static class BasicController {
 
 

+ 567 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java

@@ -0,0 +1,567 @@
+/*
+ * Copyright 2002-2022 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.configurers;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.security.config.Customizer.withDefaults;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public class HttpSecuritySecurityMatchersTests {
+
+	AnnotationConfigWebApplicationContext context;
+
+	MockHttpServletRequest request;
+
+	MockHttpServletResponse response;
+
+	MockFilterChain chain;
+
+	@Autowired
+	FilterChainProxy springSecurityFilterChain;
+
+	@BeforeEach
+	public void setup() throws Exception {
+		this.request = new MockHttpServletRequest("GET", "");
+		this.request.setMethod("GET");
+		this.response = new MockHttpServletResponse();
+		this.chain = new MockFilterChain();
+		mockMvcPresentClasspath(true);
+	}
+
+	@AfterEach
+	public void cleanup() {
+		if (this.context != null) {
+			this.context.close();
+		}
+	}
+
+	@Test
+	public void securityMatcherWhenMvcThenMvcMatcher() throws Exception {
+		loadConfig(SecurityMatcherMvcConfig.class, LegacyMvcMatchingConfig.class);
+		this.request.setServletPath("/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path.html");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path/");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+	}
+
+	@Test
+	public void securityMatcherWhenNoMvcThenAntMatcher() throws Exception {
+		mockMvcPresentClasspath(false);
+		loadConfig(SecurityMatcherNoMvcConfig.class, LegacyMvcMatchingConfig.class);
+		this.request.setServletPath("/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path.html");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+		setup();
+		this.request.setServletPath("/path/");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+	}
+
+	@Test
+	public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() {
+		loadConfig(SecurityMatcherMvcConfig.class);
+		assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty();
+	}
+
+	@Test
+	public void securityMatchersWhenMvcThenMvcMatcher() throws Exception {
+		loadConfig(SecurityMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
+		this.request.setServletPath("/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path.html");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path/");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+	}
+
+	@Test
+	public void securityMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception {
+		loadConfig(SecurityMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
+		this.request.setServletPath("/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path.html");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("/path/");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+	}
+
+	@Test
+	public void securityMatchersMvcMatcherServletPath() throws Exception {
+		loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class);
+		this.request.setServletPath("/spring");
+		this.request.setRequestURI("/spring/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("");
+		this.request.setRequestURI("/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+		setup();
+		this.request.setServletPath("/other");
+		this.request.setRequestURI("/other/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+	}
+
+	@Test
+	public void securityMatchersWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception {
+		loadConfig(SecurityMatchersMvcMatcherServletPathInLambdaConfig.class);
+		this.request.setServletPath("/spring");
+		this.request.setRequestURI("/spring/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setServletPath("");
+		this.request.setRequestURI("/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+		setup();
+		this.request.setServletPath("/other");
+		this.request.setRequestURI("/other/path");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+	}
+
+	@Test
+	public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
+		loadConfig(MultiMvcMatcherInLambdaConfig.class);
+		this.request.setRequestURI("/test-1");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setRequestURI("/test-2");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setRequestURI("/test-3");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+	}
+
+	@Test
+	public void securityMatchersWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception {
+		loadConfig(MultiMvcMatcherConfig.class);
+		this.request.setRequestURI("/test-1");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setRequestURI("/test-2");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+		setup();
+		this.request.setRequestURI("/test-3");
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+	}
+
+	public void loadConfig(Class<?>... configs) {
+		this.context = new AnnotationConfigWebApplicationContext();
+		this.context.register(configs);
+		this.context.setServletContext(new MockServletContext());
+		this.context.refresh();
+		this.context.getAutowireCapableBeanFactory().autowireBean(this);
+	}
+
+	private void mockMvcPresentClasspath(Object newValue) throws Exception {
+		mockMvcPresentClasspath(HttpSecurity.class, newValue);
+		mockMvcPresentClasspath(AbstractRequestMatcherRegistry.class, newValue);
+	}
+
+	private void mockMvcPresentClasspath(Class<?> clazz, Object newValue) throws Exception {
+		Field mvcPresentField = clazz.getDeclaredField("mvcPresent");
+		mvcPresentField.setAccessible(true);
+		Field modifiersField = Field.class.getDeclaredField("modifiers");
+		modifiersField.setAccessible(true);
+		modifiersField.setInt(mvcPresentField, mvcPresentField.getModifiers() & ~Modifier.FINAL);
+		mvcPresentField.set(null, newValue);
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	static class MultiMvcMatcherInLambdaConfig {
+
+		@Bean
+		@Order(Ordered.HIGHEST_PRECEDENCE)
+		SecurityFilterChain first(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatchers((requests) -> requests
+					.requestMatchers("/test-1")
+					.requestMatchers("/test-2")
+					.requestMatchers("/test-3")
+				)
+				.authorizeHttpRequests((authorize) -> authorize.anyRequest().denyAll())
+				.httpBasic(withDefaults());
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		SecurityFilterChain second(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatchers((requests) -> requests
+					.requestMatchers("/test-1")
+				)
+				.authorizeHttpRequests((authorize) -> authorize
+					.anyRequest().permitAll()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping({ "/test-1", "/test-2", "/test-3" })
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	static class MultiMvcMatcherConfig {
+
+		@Bean
+		@Order(Ordered.HIGHEST_PRECEDENCE)
+		SecurityFilterChain first(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatchers()
+					.requestMatchers("/test-1")
+					.requestMatchers("/test-2")
+					.requestMatchers("/test-3")
+					.and()
+				.authorizeHttpRequests()
+					.anyRequest().denyAll()
+					.and()
+				.httpBasic(withDefaults());
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		SecurityFilterChain second(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatchers()
+					.requestMatchers("/test-1")
+					.and()
+				.authorizeHttpRequests()
+					.anyRequest().permitAll();
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping({ "/test-1", "/test-2", "/test-3" })
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@EnableWebMvc
+	@Configuration
+	@Import(UsersConfig.class)
+	static class SecurityMatcherMvcConfig {
+
+		@Bean
+		SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatcher("/path")
+				.httpBasic().and()
+				.authorizeHttpRequests()
+					.anyRequest().denyAll();
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/path")
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@Import(UsersConfig.class)
+	static class SecurityMatcherNoMvcConfig {
+
+		@Bean
+		SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+					.securityMatcher("/path")
+					.httpBasic().and()
+					.authorizeHttpRequests()
+					.anyRequest().denyAll();
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/path")
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	@Import(UsersConfig.class)
+	static class SecurityMatchersMvcMatcherConfig {
+
+		@Bean
+		SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatchers()
+					.requestMatchers("/path")
+					.and()
+				.httpBasic().and()
+				.authorizeHttpRequests()
+					.anyRequest().denyAll();
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/path")
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	static class SecurityMatchersMvcMatcherInLambdaConfig {
+
+		@Bean
+		SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.securityMatchers((matchers) -> matchers
+					.requestMatchers("/path")
+				)
+				.httpBasic(withDefaults())
+				.authorizeHttpRequests((authorize) -> authorize
+					.anyRequest().denyAll()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/path")
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	@Import(UsersConfig.class)
+	static class SecurityMatchersMvcMatcherServletPathConfig {
+
+		@Bean
+		SecurityFilterChain appSecurity(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
+			MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector)
+					.servletPath("/spring");
+			// @formatter:off
+			http
+				.securityMatchers()
+					.requestMatchers(mvcMatcherBuilder.pattern("/path"))
+					.requestMatchers(mvcMatcherBuilder.pattern("/never-match"))
+					.and()
+				.httpBasic().and()
+				.authorizeHttpRequests()
+					.anyRequest().denyAll();
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/path")
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	@Import(UsersConfig.class)
+	static class SecurityMatchersMvcMatcherServletPathInLambdaConfig {
+
+		@Bean
+		SecurityFilterChain appSecurity(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
+			MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector)
+					.servletPath("/spring");
+			// @formatter:off
+			http
+				.securityMatchers((matchers) -> matchers
+					.requestMatchers(mvcMatcherBuilder.pattern("/path"))
+					.requestMatchers(mvcMatcherBuilder.pattern("/never-match"))
+				)
+				.httpBasic(withDefaults())
+				.authorizeHttpRequests((authorize) -> authorize
+					.anyRequest().denyAll()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/path")
+			String path() {
+				return "path";
+			}
+
+		}
+
+	}
+
+	@Configuration
+	static class UsersConfig {
+
+		@Bean
+		UserDetailsService userDetailsService() {
+			UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")
+					.build();
+			return new InMemoryUserDetailsManager(user);
+		}
+
+	}
+
+	@Configuration
+	static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
+
+		@Override
+		public void configurePathMatch(PathMatchConfigurer configurer) {
+			configurer.setUseSuffixPatternMatch(true);
+			configurer.setUseTrailingSlashMatch(true);
+		}
+
+	}
+
+}

+ 33 - 0
config/src/test/kotlin/org/springframework/security/config/annotation/web/CsrfDslTests.kt

@@ -44,6 +44,7 @@ import org.springframework.test.web.servlet.MockMvc
 import org.springframework.test.web.servlet.post
 import org.springframework.test.web.servlet.post
 import org.springframework.web.bind.annotation.PostMapping
 import org.springframework.web.bind.annotation.PostMapping
 import org.springframework.web.bind.annotation.RestController
 import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
 
 
 /**
 /**
  * Tests for [CsrfDsl]
  * Tests for [CsrfDsl]
@@ -279,6 +280,38 @@ class CsrfDslTests {
         }
         }
     }
     }
 
 
+    @Test
+    fun `CSRF when ignoring request matchers pattern then CSRF disabled on matching requests`() {
+        this.spring.register(IgnoringRequestMatchersPatternConfig::class.java, BasicController::class.java).autowire()
+
+        this.mockMvc.post("/test1")
+            .andExpect {
+                status { isForbidden() }
+            }
+
+        this.mockMvc.post("/test2")
+            .andExpect {
+                status { isOk() }
+            }
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class IgnoringRequestMatchersPatternConfig {
+
+        @Bean
+        open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+            http {
+                csrf {
+                    requireCsrfProtectionMatcher = AntPathRequestMatcher("/**")
+                    ignoringRequestMatchers("/test2")
+                }
+            }
+            return http.build()
+        }
+    }
+
     @RestController
     @RestController
     internal class BasicController {
     internal class BasicController {
         @PostMapping("/test1")
         @PostMapping("/test1")

+ 6 - 5
docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc

@@ -109,13 +109,14 @@ SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthoriza
 
 
 @Bean
 @Bean
 AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
 AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
+    MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
     RequestMatcher permitAll =
     RequestMatcher permitAll =
             new AndRequestMatcher(
             new AndRequestMatcher(
-                    new MvcRequestMatcher(introspector, "/resources/**"),
-                    new MvcRequestMatcher(introspector, "/signup"),
-                    new MvcRequestMatcher(introspector, "/about"));
-    RequestMatcher admin = new MvcRequestMatcher(introspector, "/admin/**");
-    RequestMatcher db = new MvcRequestMatcher(introspector, "/db/**");
+                    mvcMatcherBuilder.pattern("/resources/**"),
+                    mvcMatcherBuilder.pattern("/signup"),
+                    mvcMatcherBuilder.pattern("/about"));
+    RequestMatcher admin = mvcMatcherBuilder.pattern("/admin/**");
+    RequestMatcher db = mvcMatcherBuilder.pattern("/db/**");
     RequestMatcher any = AnyRequestMatcher.INSTANCE;
     RequestMatcher any = AnyRequestMatcher.INSTANCE;
     AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder()
     AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder()
             .add(permitAll, (context) -> new AuthorizationDecision(true))
             .add(permitAll, (context) -> new AuthorizationDecision(true))

+ 2 - 2
docs/modules/ROOT/pages/servlet/authorization/expression-based.adoc

@@ -144,7 +144,7 @@ You could then refer to the method as follows:
 ----
 ----
 http
 http
     .authorizeHttpRequests(authorize -> authorize
     .authorizeHttpRequests(authorize -> authorize
-        .antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
+        .requestMatchers("/user/**").access("@webSecurity.check(authentication,request)")
         ...
         ...
     )
     )
 ----
 ----
@@ -210,7 +210,7 @@ You could then refer to the method as follows:
 ----
 ----
 http
 http
 	.authorizeHttpRequests(authorize -> authorize
 	.authorizeHttpRequests(authorize -> authorize
-		.antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
+		.requestMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
 		...
 		...
 	);
 	);
 ----
 ----

+ 20 - 14
docs/modules/ROOT/pages/servlet/integrations/mvc.adoc

@@ -151,8 +151,8 @@ To restrict access to this controller method to admin users, you can provide aut
 @Bean
 @Bean
 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 	http
 	http
-		.authorizeHttpRequests(authorize -> authorize
-			.antMatchers("/admin").hasRole("ADMIN")
+		.authorizeHttpRequests((authorize) -> authorize
+			.requestMatchers("/admin").hasRole("ADMIN")
 		);
 		);
 	return http.build();
 	return http.build();
 }
 }
@@ -164,8 +164,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 @Bean
 @Bean
 open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 open fun filterChain(http: HttpSecurity): SecurityFilterChain {
     http {
     http {
-        authorizeRequests {
-            authorize(AntPathRequestMatcher("/admin"), hasRole("ADMIN"))
+        authorizeHttpRequests {
+            authorize("/admin", hasRole("ADMIN"))
         }
         }
     }
     }
     return http.build()
     return http.build()
@@ -191,20 +191,24 @@ Additionally, depending on our Spring MVC configuration, the `/admin` URL also m
 The problem is that our security rule protects only  `/admin`.
 The problem is that our security rule protects only  `/admin`.
 We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
 We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
 
 
-Instead, we can use Spring Security's `MvcRequestMatcher`.
-The following configuration protects the same URLs that Spring MVC matches on by using Spring MVC to match on the URL.
+Fortunately, when using the `requestMatchers` DSL method, Spring Security automatically creates a `MvcRequestMatcher` if it detects that Spring MVC is available in the classpath.
+Therefore, it will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
+
+One common requirement when using Spring MVC is to specify the servlet path property, for that you can use the `MvcRequestMatcher.Builder` to create multiple `MvcRequestMatcher` instances that share the same servlet path:
 
 
 ====
 ====
 .Java
 .Java
 [source,java,role="primary"]
 [source,java,role="primary"]
 ----
 ----
 @Bean
 @Bean
-public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
+	MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector).servletPath("/path");
 	http
 	http
-		.authorizeHttpRequests(authorize -> authorize
-			.mvcMatchers("/admin").hasRole("ADMIN")
+		.authorizeHttpRequests((authorize) -> authorize
+			.requestMatchers(mvcMatcherBuilder.pattern("/admin")).hasRole("ADMIN")
+			.requestMatchers(mvcMatcherBuilder.pattern("/user")).hasRole("USER")
 		);
 		);
-	// ...
+	return http.build();
 }
 }
 ----
 ----
 
 
@@ -212,13 +216,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 [source,kotlin,role="secondary"]
 [source,kotlin,role="secondary"]
 ----
 ----
 @Bean
 @Bean
-open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+open fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain {
+    val mvcMatcherBuilder = MvcRequestMatcher.Builder(introspector)
     http {
     http {
-        authorizeRequests {
-            authorize("/admin", hasRole("ADMIN"))
+        authorizeHttpRequests {
+            authorize(mvcMatcherBuilder.pattern("/admin"), hasRole("ADMIN"))
+            authorize(mvcMatcherBuilder.pattern("/user"), hasRole("USER"))
         }
         }
     }
     }
-    // ...
+    return http.build()
 }
 }
 ----
 ----
 ====
 ====

+ 2 - 2
docs/modules/ROOT/pages/servlet/integrations/websocket.adoc

@@ -584,7 +584,7 @@ public class WebSecurityConfig {
         http
         http
             .csrf(csrf -> csrf
             .csrf(csrf -> csrf
                 // ignore our stomp endpoints since they are protected using Stomp headers
                 // ignore our stomp endpoints since they are protected using Stomp headers
-                .ignoringAntMatchers("/chat/**")
+                .ignoringRequestMatchers("/chat/**")
             )
             )
             .headers(headers -> headers
             .headers(headers -> headers
                 // allow same origin to frame our site to support iframe SockJS
                 // allow same origin to frame our site to support iframe SockJS
@@ -610,7 +610,7 @@ open class WebSecurityConfig {
     open fun filterChain(http: HttpSecurity): SecurityFilterChain {
     open fun filterChain(http: HttpSecurity): SecurityFilterChain {
         http {
         http {
             csrf {
             csrf {
-                ignoringAntMatchers("/chat/**")
+                ignoringRequestMatchers("/chat/**")
             }
             }
             headers {
             headers {
                 frameOptions {
                 frameOptions {

+ 1 - 1
docs/modules/ROOT/pages/whats-new.adoc

@@ -17,4 +17,4 @@ Reorganize imports
 Reorganize imports
 Reorganize imports
 * https://github.com/spring-projects/spring-security/issues/11026[gh-11026] - Use `RequestAttributeSecurityContextRepository` instead of `NullSecurityContextRepository`
 * https://github.com/spring-projects/spring-security/issues/11026[gh-11026] - Use `RequestAttributeSecurityContextRepository` instead of `NullSecurityContextRepository`
 * https://github.com/spring-projects/spring-security/pull/11887[gh-11827] - Change default authority for `oauth2Login()`
 * https://github.com/spring-projects/spring-security/pull/11887[gh-11827] - Change default authority for `oauth2Login()`
-* https://github.com/spring-projects/spring-security/issues/10347[gh-10347] - Remove `UsernamePasswordAuthenticationToken` check in `BasicAuthenticationFilter`
+* https://github.com/spring-projects/spring-security/issues/10347[gh-10347] - Remove `UsernamePasswordAuthenticationToken` check in `BasicAuthenticationFilter`

+ 56 - 1
web/src/main/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2022 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -172,4 +172,59 @@ public class MvcRequestMatcher implements RequestMatcher, RequestVariablesExtrac
 
 
 	}
 	}
 
 
+	/**
+	 * A builder for {@link MvcRequestMatcher}
+	 *
+	 * @author Marcus Da Coregio
+	 * @since 5.8
+	 */
+	public static final class Builder {
+
+		private final HandlerMappingIntrospector introspector;
+
+		private String servletPath;
+
+		/**
+		 * Construct a new instance of this builder
+		 */
+		public Builder(HandlerMappingIntrospector introspector) {
+			this.introspector = introspector;
+		}
+
+		/**
+		 * Sets the servlet path to be used by the {@link MvcRequestMatcher} generated by
+		 * this builder
+		 * @param servletPath the servlet path to use
+		 * @return the {@link Builder} for further configuration
+		 */
+		public Builder servletPath(String servletPath) {
+			this.servletPath = servletPath;
+			return this;
+		}
+
+		/**
+		 * Creates an {@link MvcRequestMatcher} that uses the provided pattern to match
+		 * @param pattern the pattern used to match
+		 * @return the generated {@link MvcRequestMatcher}
+		 */
+		public MvcRequestMatcher pattern(String pattern) {
+			return pattern(null, pattern);
+		}
+
+		/**
+		 * Creates an {@link MvcRequestMatcher} that uses the provided pattern and HTTP
+		 * method to match
+		 * @param method the {@link HttpMethod}, can be null
+		 * @param pattern the patterns used to match
+		 * @return the generated {@link MvcRequestMatcher}
+		 */
+		public MvcRequestMatcher pattern(HttpMethod method, String pattern) {
+			MvcRequestMatcher mvcRequestMatcher = new MvcRequestMatcher(this.introspector, pattern);
+			mvcRequestMatcher.setServletPath(this.servletPath);
+			mvcRequestMatcher.setMethod(method);
+			return mvcRequestMatcher;
+		}
+
+	}
+
 }
 }

+ 28 - 1
web/src/test/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcherTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2022 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
 
 
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpMethod;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 import org.springframework.web.servlet.handler.MatchableHandlerMapping;
 import org.springframework.web.servlet.handler.MatchableHandlerMapping;
@@ -245,4 +246,30 @@ public class MvcRequestMatcherTests {
 		assertThat(this.matcher.matcher(this.request).isMatch()).isTrue();
 		assertThat(this.matcher.matcher(this.request).isMatch()).isTrue();
 	}
 	}
 
 
+	@Test
+	public void builderWhenServletPathThenServletPathPresent() {
+		MvcRequestMatcher matcher = new MvcRequestMatcher.Builder(this.introspector).servletPath("/path")
+				.pattern("/endpoint");
+		assertThat(matcher.getServletPath()).isEqualTo("/path");
+		assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/endpoint");
+		assertThat(ReflectionTestUtils.getField(matcher, "method")).isNull();
+	}
+
+	@Test
+	public void builderWhenPatternThenPatternPresent() {
+		MvcRequestMatcher matcher = new MvcRequestMatcher.Builder(this.introspector).pattern("/endpoint");
+		assertThat(matcher.getServletPath()).isNull();
+		assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/endpoint");
+		assertThat(ReflectionTestUtils.getField(matcher, "method")).isNull();
+	}
+
+	@Test
+	public void builderWhenMethodAndPatternThenMethodAndPatternPresent() {
+		MvcRequestMatcher matcher = new MvcRequestMatcher.Builder(this.introspector).pattern(HttpMethod.GET,
+				"/endpoint");
+		assertThat(matcher.getServletPath()).isNull();
+		assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/endpoint");
+		assertThat(ReflectionTestUtils.getField(matcher, "method")).isEqualTo(HttpMethod.GET);
+	}
+
 }
 }