Răsfoiți Sursa

Add MvcRequestMatcher

Fixes gh-3964
Rob Winch 9 ani în urmă
părinte
comite
e4c13e3c0e
26 a modificat fișierele cu 918 adăugiri și 54 ștergeri
  1. 5 4
      config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java
  2. 60 0
      config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java
  3. 30 9
      config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
  4. 10 5
      config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java
  5. 8 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
  6. 8 5
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java
  7. 13 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
  8. 10 2
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java
  9. 13 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java
  10. 1 1
      config/src/main/java/org/springframework/security/config/http/FilterChainBeanDefinitionParser.java
  11. 1 1
      config/src/main/java/org/springframework/security/config/http/FilterChainMapBeanDefinitionDecorator.java
  12. 4 2
      config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java
  13. 51 0
      config/src/main/java/org/springframework/security/config/http/HandlerMappingIntrospectorFactoryBean.java
  14. 1 1
      config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
  15. 1 1
      config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java
  16. 29 4
      config/src/main/java/org/springframework/security/config/http/MatcherType.java
  17. 2 2
      config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc
  18. 16 12
      config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd
  19. 1 1
      config/src/test/groovy/org/springframework/security/config/annotation/web/builders/DisableUseExpressionsConfig.java
  20. 77 0
      config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy
  21. 112 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
  22. 73 1
      docs/manual/src/docs/asciidoc/index.adoc
  23. 25 0
      web/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java
  24. 127 0
      web/src/main/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.java
  25. 38 0
      web/src/main/java/org/springframework/security/web/util/matcher/RequestVariablesExtractor.java
  26. 202 0
      web/src/test/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcherTests.java

+ 5 - 4
config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java

@@ -25,6 +25,7 @@ import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.DelegatingFilterProxy;
@@ -57,7 +58,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 	private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
 	private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<SecurityConfigurer<O, B>>();
 
-	private final Map<Class<Object>, Object> sharedObjects = new HashMap<Class<Object>, Object>();
+	private final Map<Class<? extends Object>, Object> sharedObjects = new HashMap<Class<? extends Object>, Object>();
 
 	private final boolean allowConfigurersOfSameType;
 
@@ -155,7 +156,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 	 */
 	@SuppressWarnings("unchecked")
 	public <C> void setSharedObject(Class<C> sharedType, C object) {
-		this.sharedObjects.put((Class<Object>) sharedType, object);
+		this.sharedObjects.put(sharedType, object);
 	}
 
 	/**
@@ -173,7 +174,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 	 * Gets the shared objects
 	 * @return
 	 */
-	public Map<Class<Object>, Object> getSharedObjects() {
+	public Map<Class<? extends Object>, Object> getSharedObjects() {
 		return Collections.unmodifiableMap(this.sharedObjects);
 	}
 
@@ -300,7 +301,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
 	 * @return the possibly modified Object to use
 	 */
 	protected <P> P postProcess(P object) {
-		return (P) this.objectPostProcessor.postProcess(object);
+		return this.objectPostProcessor.postProcess(object);
 	}
 
 	/**

+ 60 - 0
config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java

@@ -19,12 +19,15 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry;
+import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AnyRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
 
 /**
  * A base class for registering {@link RequestMatcher}'s. For example, it might allow for
@@ -39,6 +42,12 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
 public abstract class AbstractRequestMatcherRegistry<C> {
 	private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
 
+	private ApplicationContext context;
+
+	protected final void setApplicationContext(ApplicationContext context) {
+		this.context = context;
+	}
+
 	/**
 	 * Maps any request.
 	 *
@@ -92,6 +101,57 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 		return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
 	}
 
+	/**
+	 * <p>
+	 * Maps 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.
+	 * </p>
+	 * <p>
+	 * If the current request will not be processed by Spring MVC, a reasonable default
+	 * using the pattern as a ant pattern will be used.
+	 * </p>
+	 *
+	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
+	 * Spring MVC
+	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 */
+	public C mvcMatchers(String... mvcPatterns) {
+		return mvcMatchers(null, mvcPatterns);
+	}
+
+	/**
+	 * <p>
+	 * Maps 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.
+	 * </p>
+	 * <p>
+	 * If the current request will not be processed by Spring MVC, a reasonable default
+	 * using the pattern as a ant pattern will be used.
+	 * </p>
+	 *
+	 * @param method the HTTP method to match on
+	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
+	 * Spring MVC
+	 * @return the object that is chained after creating the {@link RequestMatcher}.
+	 */
+	public C mvcMatchers(HttpMethod method, String... mvcPatterns) {
+		HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(
+				this.context);
+		List<RequestMatcher> matchers = new ArrayList<RequestMatcher>(mvcPatterns.length);
+		for (String mvcPattern : mvcPatterns) {
+			MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
+			if (method != null) {
+				matcher.setMethod(method);
+			}
+			matchers.add(matcher);
+		}
+		return chainRequestMatchers(matchers);
+	}
+
 	/**
 	 * Maps a {@link List} of
 	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher}

+ 30 - 9
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -23,6 +23,7 @@ import java.util.Map;
 import javax.servlet.Filter;
 import javax.servlet.http.HttpServletRequest;
 
+import org.springframework.context.ApplicationContext;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
@@ -62,6 +63,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.web.DefaultSecurityFilterChain;
 import org.springframework.security.web.PortMapper;
 import org.springframework.security.web.PortMapperImpl;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
 import org.springframework.security.web.session.HttpSessionEventPublisher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -113,7 +115,7 @@ public final class HttpSecurity extends
 		AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
 		implements SecurityBuilder<DefaultSecurityFilterChain>,
 		HttpSecurityBuilder<HttpSecurity> {
-	private final RequestMatcherConfigurer requestMatcherConfigurer = new RequestMatcherConfigurer();
+	private final RequestMatcherConfigurer requestMatcherConfigurer;
 	private List<Filter> filters = new ArrayList<Filter>();
 	private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
 	private FilterComparator comparator = new FilterComparator();
@@ -126,15 +128,24 @@ public final class HttpSecurity extends
 	 * @param sharedObjects the shared Objects to initialize the {@link HttpSecurity} with
 	 * @see WebSecurityConfiguration
 	 */
+	@SuppressWarnings("unchecked")
 	public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
 			AuthenticationManagerBuilder authenticationBuilder,
-			Map<Class<Object>, Object> sharedObjects) {
+			Map<Class<? extends Object>, Object> sharedObjects) {
 		super(objectPostProcessor);
 		Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
 		setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
-		for (Map.Entry<Class<Object>, Object> entry : sharedObjects.entrySet()) {
-			setSharedObject(entry.getKey(), entry.getValue());
+		for (Map.Entry<Class<? extends Object>, Object> entry : sharedObjects
+				.entrySet()) {
+			setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
 		}
+		ApplicationContext context = (ApplicationContext) sharedObjects
+				.get(ApplicationContext.class);
+		this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
+	}
+
+	private ApplicationContext getContext() {
+		return getSharedObject(ApplicationContext.class);
 	}
 
 	/**
@@ -634,7 +645,8 @@ public final class HttpSecurity extends
 	 */
 	public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
 			throws Exception {
-		return getOrApply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>())
+		ApplicationContext context = getContext();
+		return getOrApply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>(context))
 				.getRegistry();
 	}
 
@@ -710,7 +722,8 @@ public final class HttpSecurity extends
 	 * @throws Exception
 	 */
 	public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
-		return getOrApply(new CsrfConfigurer<HttpSecurity>());
+		ApplicationContext context = getContext();
+		return getOrApply(new CsrfConfigurer<HttpSecurity>(context));
 	}
 
 	/**
@@ -917,7 +930,9 @@ public final class HttpSecurity extends
 	 */
 	public ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry requiresChannel()
 			throws Exception {
-		return getOrApply(new ChannelSecurityConfigurer<HttpSecurity>()).getRegistry();
+		ApplicationContext context = getContext();
+		return getOrApply(new ChannelSecurityConfigurer<HttpSecurity>(context))
+				.getRegistry();
 	}
 
 	/**
@@ -1241,8 +1256,16 @@ public final class HttpSecurity extends
 	 */
 	public final class RequestMatcherConfigurer extends
 			AbstractRequestMatcherRegistry<RequestMatcherConfigurer> {
+
 		private List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
 
+		/**
+		 * @param context
+		 */
+		private RequestMatcherConfigurer(ApplicationContext context) {
+			setApplicationContext(context);
+		}
+
 		protected RequestMatcherConfigurer chainRequestMatchers(
 				List<RequestMatcher> requestMatchers) {
 			matchers.addAll(requestMatchers);
@@ -1259,8 +1282,6 @@ public final class HttpSecurity extends
 			return HttpSecurity.this;
 		}
 
-		private RequestMatcherConfigurer() {
-		}
 	}
 
 	/**

+ 10 - 5
config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.beans.BeansException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
@@ -80,7 +81,7 @@ public final class WebSecurity extends
 
 	private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
 
-	private final IgnoredRequestConfigurer ignoredRequestRegistry = new IgnoredRequestConfigurer();
+	private IgnoredRequestConfigurer ignoredRequestRegistry;
 
 	private FilterSecurityInterceptor filterSecurityInterceptor;
 
@@ -316,6 +317,10 @@ public final class WebSecurity extends
 	public final class IgnoredRequestConfigurer extends
 			AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {
 
+		private IgnoredRequestConfigurer(ApplicationContext context) {
+			setApplicationContext(context);
+		}
+
 		@Override
 		protected IgnoredRequestConfigurer chainRequestMatchers(
 				List<RequestMatcher> requestMatchers) {
@@ -329,13 +334,13 @@ public final class WebSecurity extends
 		public WebSecurity and() {
 			return WebSecurity.this;
 		}
-
-		private IgnoredRequestConfigurer() {
-		}
 	}
 
+	@Override
 	public void setApplicationContext(ApplicationContext applicationContext)
 			throws BeansException {
-		defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
+		this.defaultWebSecurityExpressionHandler
+				.setApplicationContext(applicationContext);
+		this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
 	}
 }

+ 8 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

@@ -329,6 +329,14 @@ public abstract class WebSecurityConfigurerAdapter implements
 	}
 	// @formatter:on
 
+	/**
+	 * Gets the ApplicationContext
+	 * @return the context
+	 */
+	protected final ApplicationContext getApplicationContext() {
+		return this.context;
+	}
+
 	@Autowired
 	public void setApplicationContext(ApplicationContext context) {
 		this.context = context;

+ 8 - 5
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java

@@ -20,6 +20,7 @@ import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 
+import org.springframework.context.ApplicationContext;
 import org.springframework.security.access.ConfigAttribute;
 import org.springframework.security.access.SecurityConfig;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -80,13 +81,14 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
 	private LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
 	private List<ChannelProcessor> channelProcessors;
 
-	private final ChannelRequestMatcherRegistry REGISTRY = new ChannelRequestMatcherRegistry();
+	private final ChannelRequestMatcherRegistry REGISTRY;
 
 	/**
 	 * Creates a new instance
 	 * @see HttpSecurity#requiresChannel()
 	 */
-	public ChannelSecurityConfigurer() {
+	public ChannelSecurityConfigurer(ApplicationContext context) {
+		this.REGISTRY = new ChannelRequestMatcherRegistry(context);
 	}
 
 	public ChannelRequestMatcherRegistry getRegistry() {
@@ -146,6 +148,10 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
 	public final class ChannelRequestMatcherRegistry extends
 			AbstractConfigAttributeRequestMatcherRegistry<RequiresChannelUrl> {
 
+		private ChannelRequestMatcherRegistry(ApplicationContext context) {
+			setApplicationContext(context);
+		}
+
 		@Override
 		protected RequiresChannelUrl chainRequestMatchersInternal(
 				List<RequestMatcher> requestMatchers) {
@@ -185,9 +191,6 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> e
 		public H and() {
 			return ChannelSecurityConfigurer.this.and();
 		}
-
-		private ChannelRequestMatcherRegistry() {
-		}
 	}
 
 	public final class RequiresChannelUrl {

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

@@ -21,6 +21,7 @@ import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.springframework.context.ApplicationContext;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
@@ -78,12 +79,14 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
 			new HttpSessionCsrfTokenRepository());
 	private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
 	private List<RequestMatcher> ignoredCsrfProtectionMatchers = new ArrayList<RequestMatcher>();
+	private final ApplicationContext context;
 
 	/**
 	 * Creates a new instance
 	 * @see HttpSecurity#csrf()
 	 */
-	public CsrfConfigurer() {
+	public CsrfConfigurer(ApplicationContext context) {
+		this.context = context;
 	}
 
 	/**
@@ -141,7 +144,8 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
 	 * @since 4.0
 	 */
 	public CsrfConfigurer<H> ignoringAntMatchers(String... antPatterns) {
-		return new IgnoreCsrfProtectionRegistry().antMatchers(antPatterns).and();
+		return new IgnoreCsrfProtectionRegistry(this.context).antMatchers(antPatterns)
+				.and();
 	}
 
 	@SuppressWarnings("unchecked")
@@ -265,6 +269,13 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
 	private class IgnoreCsrfProtectionRegistry
 			extends AbstractRequestMatcherRegistry<IgnoreCsrfProtectionRegistry> {
 
+		/**
+		 * @param context
+		 */
+		private IgnoreCsrfProtectionRegistry(ApplicationContext context) {
+			setApplicationContext(context);
+		}
+
 		public CsrfConfigurer<H> and() {
 			return CsrfConfigurer.this;
 		}

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

@@ -84,7 +84,7 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
 	private static final String fullyAuthenticated = "fullyAuthenticated";
 	private static final String rememberMe = "rememberMe";
 
-	private final ExpressionInterceptUrlRegistry REGISTRY = new ExpressionInterceptUrlRegistry();
+	private final ExpressionInterceptUrlRegistry REGISTRY;
 
 	private SecurityExpressionHandler<FilterInvocation> expressionHandler;
 
@@ -92,7 +92,8 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
 	 * Creates a new instance
 	 * @see HttpSecurity#authorizeRequests()
 	 */
-	public ExpressionUrlAuthorizationConfigurer() {
+	public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
+		this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
 	}
 
 	public ExpressionInterceptUrlRegistry getRegistry() {
@@ -103,6 +104,13 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
 			extends
 			ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<ExpressionInterceptUrlRegistry, AuthorizedUrl> {
 
+		/**
+		 * @param context
+		 */
+		private ExpressionInterceptUrlRegistry(ApplicationContext context) {
+			setApplicationContext(context);
+		}
+
 		@Override
 		protected final AuthorizedUrl chainRequestMatchersInternal(
 				List<RequestMatcher> requestMatchers) {

+ 13 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java

@@ -19,6 +19,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import org.springframework.context.ApplicationContext;
 import org.springframework.security.access.AccessDecisionManager;
 import org.springframework.security.access.AccessDecisionVoter;
 import org.springframework.security.access.ConfigAttribute;
@@ -86,7 +87,11 @@ import org.springframework.util.Assert;
  */
 public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends
 		AbstractInterceptUrlConfigurer<UrlAuthorizationConfigurer<H>, H> {
-	private final StandardInterceptUrlRegistry REGISTRY = new StandardInterceptUrlRegistry();
+	private final StandardInterceptUrlRegistry REGISTRY;
+
+	public UrlAuthorizationConfigurer(ApplicationContext context) {
+		this.REGISTRY = new StandardInterceptUrlRegistry(context);
+	}
 
 	/**
 	 * The StandardInterceptUrlRegistry is what users will interact with after applying
@@ -114,6 +119,13 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
 			extends
 			ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<StandardInterceptUrlRegistry, AuthorizedUrl> {
 
+		/**
+		 * @param context
+		 */
+		private StandardInterceptUrlRegistry(ApplicationContext context) {
+			setApplicationContext(context);
+		}
+
 		@Override
 		protected final AuthorizedUrl chainRequestMatchersInternal(
 				List<RequestMatcher> requestMatchers) {

+ 1 - 1
config/src/main/java/org/springframework/security/config/http/FilterChainBeanDefinitionParser.java

@@ -45,7 +45,7 @@ public class FilterChainBeanDefinitionParser implements BeanDefinitionParser {
 
 		if (StringUtils.hasText(path)) {
 			Assert.isTrue(!StringUtils.hasText(requestMatcher), "");
-			builder.addConstructorArgValue(matcherType.createMatcher(path, null));
+			builder.addConstructorArgValue(matcherType.createMatcher(pc, path, null));
 		}
 		else {
 			Assert.isTrue(StringUtils.hasText(requestMatcher), "");

+ 1 - 1
config/src/main/java/org/springframework/security/config/http/FilterChainMapBeanDefinitionDecorator.java

@@ -71,7 +71,7 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
 								+ "'must not be empty", elt);
 			}
 
-			BeanDefinition matcher = matcherType.createMatcher(path, null);
+			BeanDefinition matcher = matcherType.createMatcher(parserContext, path, null);
 
 			if (filters.equals(HttpSecurityBeanDefinitionParser.OPT_FILTERS_NONE)) {
 				securityFilterChains.add(createSecurityFilterChain(matcher,

+ 4 - 2
config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java

@@ -165,7 +165,8 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
 				method = null;
 			}
 
-			BeanDefinition matcher = matcherType.createMatcher(path, method);
+			BeanDefinition matcher = matcherType.createMatcher(parserContext, path,
+					method);
 			BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder
 					.rootBeanDefinition(SecurityConfig.class);
 
@@ -194,7 +195,8 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
 
 		if (addAuthenticatedAll && filterInvocationDefinitionMap.isEmpty()) {
 
-			BeanDefinition matcher = matcherType.createMatcher("/**", null);
+			BeanDefinition matcher = matcherType.createMatcher(parserContext, "/**",
+					null);
 			BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder
 					.rootBeanDefinition(SecurityConfig.class);
 			attributeBuilder.addConstructorArgValue(new String[] { "authenticated" });

+ 51 - 0
config/src/main/java/org/springframework/security/config/http/HandlerMappingIntrospectorFactoryBean.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012-2016 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
+ *
+ *      http://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.http;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
+
+/**
+ * Used for creating an instance of {@link HandlerMappingIntrospector} and autowiring the
+ * {@link ApplicationContext}.
+ *
+ * @author Rob Winch
+ * @since 4.1.1
+ */
+class HandlerMappingIntrospectorFactoryBean implements ApplicationContextAware {
+
+	private ApplicationContext context;
+
+	HandlerMappingIntrospector createHandlerMappingIntrospector() {
+		return new HandlerMappingIntrospector(this.context);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.
+	 * springframework.context.ApplicationContext)
+	 */
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext)
+			throws BeansException {
+		this.context = applicationContext;
+	}
+
+}

+ 1 - 1
config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

@@ -613,7 +613,7 @@ class HttpConfigurationBuilder {
 			String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
 
 			if (StringUtils.hasText(requiredChannel)) {
-				BeanDefinition matcher = matcherType.createMatcher(path, method);
+				BeanDefinition matcher = matcherType.createMatcher(pc, path, method);
 
 				RootBeanDefinition channelAttributes = new RootBeanDefinition(
 						ChannelAttributeFactory.class);

+ 1 - 1
config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java

@@ -198,7 +198,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
 
 		}
 		else if (StringUtils.hasText(filterChainPattern)) {
-			filterChainMatcher = MatcherType.fromElement(element).createMatcher(
+			filterChainMatcher = MatcherType.fromElement(element).createMatcher(pc,
 					filterChainPattern, null);
 		}
 		else {

+ 29 - 4
config/src/main/java/org/springframework/security/config/http/MatcherType.java

@@ -18,6 +18,8 @@ package org.springframework.security.config.http;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AnyRequestMatcher;
 import org.springframework.security.web.util.matcher.RegexRequestMatcher;
@@ -33,17 +35,20 @@ import org.w3c.dom.Element;
  */
 public enum MatcherType {
 	ant(AntPathRequestMatcher.class), regex(RegexRequestMatcher.class), ciRegex(
-			RegexRequestMatcher.class);
+			RegexRequestMatcher.class), mvc(MvcRequestMatcher.class);
+
+	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
+	private static final String HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME = "org.springframework.security.config.http.HandlerMappingIntrospectorFactoryBean";
 
 	private static final String ATT_MATCHER_TYPE = "request-matcher";
 
-	private final Class<? extends RequestMatcher> type;
+	final Class<? extends RequestMatcher> type;
 
 	MatcherType(Class<? extends RequestMatcher> type) {
 		this.type = type;
 	}
 
-	public BeanDefinition createMatcher(String path, String method) {
+	public BeanDefinition createMatcher(ParserContext pc, String path, String method) {
 		if (("/**".equals(path) || "**".equals(path)) && method == null) {
 			return new RootBeanDefinition(AnyRequestMatcher.class);
 		}
@@ -51,8 +56,28 @@ public enum MatcherType {
 		BeanDefinitionBuilder matcherBldr = BeanDefinitionBuilder
 				.rootBeanDefinition(type);
 
+		if (this == mvc) {
+			if (!pc.getRegistry().isBeanNameInUse(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
+				BeanDefinitionBuilder hmifb = BeanDefinitionBuilder
+						.rootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class);
+				pc.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME,
+						hmifb.getBeanDefinition());
+
+				RootBeanDefinition hmi = new RootBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME);
+				hmi.setFactoryBeanName(HANDLER_MAPPING_INTROSPECTOR_FACTORY_BEAN_NAME);
+				hmi.setFactoryMethodName("createHandlerMappingIntrospector");
+				pc.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, hmi);
+			}
+			matcherBldr.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME);
+		}
+
 		matcherBldr.addConstructorArgValue(path);
-		matcherBldr.addConstructorArgValue(method);
+		if (this == mvc) {
+			matcherBldr.addPropertyValue("method", method);
+		}
+		else {
+			matcherBldr.addConstructorArgValue(method);
+		}
 
 		if (this == ciRegex) {
 			matcherBldr.addConstructorArgValue(true);

+ 2 - 2
config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc

@@ -12,8 +12,8 @@ base64 =
 	## Whether a string should be base64 encoded
 	attribute base64 {xsd:boolean}
 request-matcher =
-	## Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
-	attribute request-matcher {"ant" | "regex" | "ciRegex"}
+	## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
+	attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"}
 port =
 	## Specifies an IP port number. Used to configure an embedded LDAP server, for example.
 	attribute port { xsd:positiveInteger }

+ 16 - 12
config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd

@@ -34,13 +34,14 @@
   <xs:attributeGroup name="request-matcher">
       <xs:attribute name="request-matcher" use="required">
          <xs:annotation>
-            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
-                (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
-                case-insensitive regular expressions.
+            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+                (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+                and 'ciRegex' for case-insensitive regular expressions.
                 </xs:documentation>
          </xs:annotation>
          <xs:simpleType>
             <xs:restriction base="xs:token">
+               <xs:enumeration value="mvc"/>
                <xs:enumeration value="ant"/>
                <xs:enumeration value="regex"/>
                <xs:enumeration value="ciRegex"/>
@@ -1187,13 +1188,14 @@
       </xs:attribute>
       <xs:attribute name="request-matcher">
          <xs:annotation>
-            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
-                (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
-                case-insensitive regular expressions.
+            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+                (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+                and 'ciRegex' for case-insensitive regular expressions.
                 </xs:documentation>
          </xs:annotation>
          <xs:simpleType>
             <xs:restriction base="xs:token">
+               <xs:enumeration value="mvc"/>
                <xs:enumeration value="ant"/>
                <xs:enumeration value="regex"/>
                <xs:enumeration value="ciRegex"/>
@@ -1557,13 +1559,14 @@
   <xs:attributeGroup name="filter-chain-map.attlist">
       <xs:attribute name="request-matcher">
          <xs:annotation>
-            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
-                (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
-                case-insensitive regular expressions.
+            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+                (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+                and 'ciRegex' for case-insensitive regular expressions.
                 </xs:documentation>
          </xs:annotation>
          <xs:simpleType>
             <xs:restriction base="xs:token">
+               <xs:enumeration value="mvc"/>
                <xs:enumeration value="ant"/>
                <xs:enumeration value="regex"/>
                <xs:enumeration value="ciRegex"/>
@@ -1668,13 +1671,14 @@
       </xs:attribute>
       <xs:attribute name="request-matcher">
          <xs:annotation>
-            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'ant'
-                (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for
-                case-insensitive regular expressions.
+            <xs:documentation>Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+                (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+                and 'ciRegex' for case-insensitive regular expressions.
                 </xs:documentation>
          </xs:annotation>
          <xs:simpleType>
             <xs:restriction base="xs:token">
+               <xs:enumeration value="mvc"/>
                <xs:enumeration value="ant"/>
                <xs:enumeration value="regex"/>
                <xs:enumeration value="ciRegex"/>

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/annotation/web/builders/DisableUseExpressionsConfig.java

@@ -25,7 +25,7 @@ public class DisableUseExpressionsConfig extends BaseWebConfig {
 	protected void configure(HttpSecurity http) throws Exception {
 		// This config is also on UrlAuthorizationConfigurer javadoc
 		http
-			.apply(new UrlAuthorizationConfigurer<HttpSecurity>()).getRegistry()
+			.apply(new UrlAuthorizationConfigurer<HttpSecurity>(getApplicationContext())).getRegistry()
 				.antMatchers("/users**","/sessions/**").hasRole("USER")
 				.antMatchers("/signup").hasRole("ANONYMOUS")
 				.anyRequest().hasRole("USER");

+ 77 - 0
config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy

@@ -24,6 +24,8 @@ import org.springframework.mock.web.MockHttpServletResponse
 import org.springframework.security.access.SecurityConfig
 import org.springframework.security.crypto.codec.Base64
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 
 
 /**
@@ -197,6 +199,73 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
 		response.status == HttpServletResponse.SC_FORBIDDEN
 	}
 
+	def "intercept-url supports mvc matchers"() {
+		setup:
+		MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
+		MockHttpServletResponse response = new MockHttpServletResponse()
+		MockFilterChain chain = new MockFilterChain()
+		xml.http('request-matcher':'mvc') {
+			'http-basic'()
+			'intercept-url'(pattern: '/path', access: "denyAll")
+		}
+		bean('pathController',PathController)
+		xml.'mvc:annotation-driven'()
+
+		createAppContext()
+		when:
+		request.servletPath = "/path"
+		springSecurityFilterChain.doFilter(request, response, chain)
+		then:
+		response.status == HttpServletResponse.SC_UNAUTHORIZED
+		when:
+		request = new MockHttpServletRequest(method:'GET')
+		response = new MockHttpServletResponse()
+		chain = new MockFilterChain()
+		request.servletPath = "/path.html"
+		springSecurityFilterChain.doFilter(request, response, chain)
+		then:
+		response.status == HttpServletResponse.SC_UNAUTHORIZED
+		when:
+		request = new MockHttpServletRequest(method:'GET')
+		response = new MockHttpServletResponse()
+		chain = new MockFilterChain()
+		request.servletPath = "/path/"
+		springSecurityFilterChain.doFilter(request, response, chain)
+		then:
+		response.status == HttpServletResponse.SC_UNAUTHORIZED
+	}
+
+	def "intercept-url mvc supports path variables"() {
+		setup:
+		MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
+		MockHttpServletResponse response = new MockHttpServletResponse()
+		MockFilterChain chain = new MockFilterChain()
+		xml.http('request-matcher':'mvc') {
+			'http-basic'()
+			'intercept-url'(pattern: '/user/{un}/**', access: "#un == 'user'")
+		}
+		createAppContext()
+		when: 'user can access'
+		request.servletPath = '/user/user/abc'
+		springSecurityFilterChain.doFilter(request,response,chain)
+		then: 'The response is OK'
+		response.status == HttpServletResponse.SC_OK
+		when: 'cannot access otheruser'
+		request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
+		login(request, 'user', 'password')
+		chain.reset()
+		springSecurityFilterChain.doFilter(request,response,chain)
+		then: 'The response is OK'
+		response.status == HttpServletResponse.SC_FORBIDDEN
+		when: 'user can access case insensitive URL'
+		request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
+		login(request, 'user', 'password')
+		chain.reset()
+		springSecurityFilterChain.doFilter(request,response,chain)
+		then: 'The response is OK'
+		response.status == HttpServletResponse.SC_FORBIDDEN
+	}
+
 	public static class Id {
 		public boolean isOne(int i) {
 			return i == 1;
@@ -207,4 +276,12 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
 		String toEncode = username + ':' + password
 		request.addHeader('Authorization','Basic ' + new String(Base64.encode(toEncode.getBytes('UTF-8'))))
 	}
+
+	@RestController
+	static class PathController {
+		@RequestMapping("/path")
+		public String path() {
+			return "path";
+		}
+	}
 }

+ 112 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java

@@ -28,6 +28,7 @@ import org.springframework.http.HttpMethod;
 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.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -41,7 +42,10 @@ import org.springframework.security.core.context.SecurityContextImpl;
 import org.springframework.security.web.FilterChainProxy;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+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 static org.assertj.core.api.Assertions.assertThat;
 
@@ -249,9 +253,117 @@ public class AuthorizeRequestsTests {
 		}
 	}
 
+	@Test
+	public void mvcMatcher() throws Exception {
+		loadConfig(MvcMatcherConfig.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);
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	static class MvcMatcherConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.httpBasic().and()
+				.authorizeRequests()
+					.mvcMatchers("/path").denyAll();
+			// @formatter:on
+		}
+
+		@Override
+		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+			// @formatter:off
+			auth
+				.inMemoryAuthentication();
+			// @formatter:on
+		}
+
+		@RestController
+		static class PathController {
+			@RequestMapping("/path")
+			public String path() {
+				return "path";
+			}
+		}
+	}
+
+	@Test
+	public void mvcMatcherPathVariables() throws Exception {
+		loadConfig(MvcMatcherPathVariablesConfig.class);
+
+		this.request.setServletPath("/user/user");
+
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+
+		this.setup();
+		this.request.setServletPath("/user/deny");
+
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+
+		assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+	}
+
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	static class MvcMatcherPathVariablesConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.httpBasic().and()
+				.authorizeRequests()
+					.mvcMatchers("/user/{userName}").access("#userName == 'user'");
+			// @formatter:on
+		}
+
+		@Override
+		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+			// @formatter:off
+			auth
+				.inMemoryAuthentication();
+			// @formatter:on
+		}
+
+		@RestController
+		static class PathController {
+			@RequestMapping("/path")
+			public String path() {
+				return "path";
+			}
+		}
+	}
+
 	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);

+ 73 - 1
docs/manual/src/docs/asciidoc/index.adoc

@@ -389,6 +389,7 @@ Here is the list of improvements:
 * Ability to add a `Filter` at a specific location in the chain using `HttpSecurity.addFilterAt`
 
 === Web Application Security Improvements
+* <<mvc-requestmatcher,MvcRequestMatcher>>
 * <<headers-csp,Content Security Policy (CSP)>>
 * <<headers-hpkp,HTTP Public Key Pinning (HPKP)>>
 * <<cors,CORS>>
@@ -6726,6 +6727,77 @@ To enable Spring Security integration with Spring MVC add the `@EnableWebSecurit
 
 NOTE: Spring Security provides the configuration using Spring MVC's http://docs.spring.io/spring-framework/docs/4.1.x/spring-framework-reference/htmlsingle/#mvc-config-customize[WebMvcConfigurerAdapter]. This means that if you are using more advanced options, like integrating with `WebMvcConfigurationSupport` directly, then you will need to manually provide the Spring Security configuration.
 
+[[mvc-requestmatcher]]
+=== MvcRequestMatcher
+
+Spring Security provides deep integration with how Spring MVC matches on URLs with `MvcRequestMatcher`.
+This is helpful to ensure your Security rules match the logic used to handle your requests.
+
+[NOTE]
+====
+It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.
+
+Providing authorization rules by matching on `HttpServletRequest` is good because it happens very early in the code path and helps reduce the https://en.wikipedia.org/wiki/Attack_surface[attack surface].
+Method security ensures that if someone has bypassed the web authorization rules, that your application is still secured.
+This is what is known as https://en.wikipedia.org/wiki/Defense_in_depth_(computing)[Defence in Depth]
+====
+
+Consider a controller that is mapped as follows:
+
+[source,java]
+----
+@RequestMapping("/admin")
+public String admin() {
+----
+
+If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the `HttpServletRequest` with the following:
+
+[source,java]
+----
+protected configure(HttpSecurity http) throws Exception {
+	http
+		.authorizeRequests()
+			.antMatchers("/admin").hasRole("ADMIN");
+}
+----
+
+or in XML
+
+[source,xml]
+----
+<http>
+	<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
+</http>
+----
+
+With either configuration, the URL `/admin` will require the authenticated user to be an admin user.
+However, depending on our Spring MVC configuration, the URL `/admin.html` will also map to our `admin()` method.
+Additionally, depending on our Spring MVC configuration, the URL `/admin/` will also map to our `admin()` method.
+
+The problem is that our security rule is only protecting `/admin`.
+We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
+
+Instead, we can leverage Spring Security's `MvcRequestMatcher`.
+The following configuration will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
+
+
+[source,java]
+----
+protected configure(HttpSecurity http) throws Exception {
+	http
+		.authorizeRequests()
+			.mvcMatchers("/admin").hasRole("ADMIN");
+}
+----
+
+or in XML
+
+[source,xml]
+----
+<http request-matcher="mvc">
+	<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
+</http>
+----
 
 [[mvc-authentication-principal]]
 === @AuthenticationPrincipal
@@ -7403,7 +7475,7 @@ Sets the realm name used for basic authentication (if enabled). Corresponds to t
 
 [[nsa-http-request-matcher]]
 * **request-matcher**
-Defines the `RequestMatcher` strategy used in the `FilterChainProxy` and the beans created by the `intercept-url` to match incoming requests. Options are currently `ant`, `regex` and `ciRegex`, for ant, regular-expression and case-insensitive regular-expression repsectively. A separate instance is created for each<<nsa-intercept-url,intercept-url>> element using its <<nsa-intercept-url-pattern,pattern>> and <<nsa-intercept-url-method,method>> attributes. Ant paths are matched using an `AntPathRequestMatcher` and regular expressions are matched using a `RegexRequestMatcher`. See the Javadoc for these classes for more details on exactly how the matching is preformed. Ant paths are the default strategy.
+Defines the `RequestMatcher` strategy used in the `FilterChainProxy` and the beans created by the `intercept-url` to match incoming requests. Options are currently `mvc`, `ant`, `regex` and `ciRegex`, for Spring MVC, ant, regular-expression and case-insensitive regular-expression respectively. A separate instance is created for each<<nsa-intercept-url,intercept-url>> element using its <<nsa-intercept-url-pattern,pattern>> and <<nsa-intercept-url-method,method>> attributes. Ant paths are matched using an `AntPathRequestMatcher` and regular expressions are matched using a `RegexRequestMatcher`. See the Javadoc for these classes for more details on exactly how the matching is performed. Ant paths are the default strategy.
 
 
 [[nsa-http-request-matcher-ref]]

+ 25 - 0
web/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java

@@ -33,6 +33,7 @@ import org.springframework.security.web.FilterInvocation;
 import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.security.web.util.matcher.RequestVariablesExtractor;
 import org.springframework.util.Assert;
 
 /**
@@ -96,6 +97,10 @@ public final class ExpressionBasedFilterInvocationSecurityMetadataSource
 			return new AntPathMatcherEvaluationContextPostProcessor(
 					(AntPathRequestMatcher) request);
 		}
+		if (request instanceof RequestVariablesExtractor) {
+			return new RequestVariablesExtractorEvaluationContextPostProcessor(
+					(RequestVariablesExtractor) request);
+		}
 		return null;
 	}
 
@@ -119,4 +124,24 @@ public final class ExpressionBasedFilterInvocationSecurityMetadataSource
 		}
 	}
 
+	static class RequestVariablesExtractorEvaluationContextPostProcessor
+			extends AbstractVariableEvaluationContextPostProcessor {
+		private final RequestVariablesExtractor matcher;
+
+		public RequestVariablesExtractorEvaluationContextPostProcessor(
+				RequestVariablesExtractor matcher) {
+			this.matcher = matcher;
+		}
+
+		@Override
+		Map<String, String> extractVariables(HttpServletRequest request) {
+			return this.matcher.extractUriTemplateVariables(request);
+		}
+
+		@Override
+		String postProcessVariableName(String variableName) {
+			return variableName;
+		}
+	}
+
 }

+ 127 - 0
web/src/main/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.java

@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012-2016 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
+ *
+ *      http://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.web.servlet.util.matcher;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.security.web.util.matcher.RequestVariablesExtractor;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
+import org.springframework.web.servlet.handler.MatchableHandlerMapping;
+import org.springframework.web.servlet.handler.RequestMatchResult;
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ * A {@link RequestMatcher} that uses Spring MVC's {@link HandlerMappingIntrospector} to
+ * match the path and extract variables.
+ *
+ * @author Rob Winch
+ * @since 4.1.1
+ */
+public final class MvcRequestMatcher
+		implements RequestMatcher, RequestVariablesExtractor {
+	private final DefaultMatcher defaultMatcher = new DefaultMatcher();
+
+	private final HandlerMappingIntrospector introspector;
+	private final String pattern;
+	private HttpMethod method;
+
+	public MvcRequestMatcher(HandlerMappingIntrospector introspector, String pattern) {
+		this.introspector = introspector;
+		this.pattern = pattern;
+	}
+
+	@Override
+	public boolean matches(HttpServletRequest request) {
+		if (this.method != null && !this.method.name().equals(request.getMethod())) {
+			return false;
+		}
+		MatchableHandlerMapping mapping = getMapping(request);
+		if (mapping == null) {
+			return this.defaultMatcher.matches(request);
+		}
+		RequestMatchResult matchResult = mapping.match(request, this.pattern);
+		return matchResult != null;
+	}
+
+	private MatchableHandlerMapping getMapping(HttpServletRequest request) {
+		try {
+			return this.introspector.getMatchableHandlerMapping(request);
+		}
+		catch (Throwable t) {
+			return null;
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.springframework.security.web.util.matcher.RequestVariablesExtractor#
+	 * extractUriTemplateVariables(javax.servlet.http.HttpServletRequest)
+	 */
+	@Override
+	public Map<String, String> extractUriTemplateVariables(HttpServletRequest request) {
+		MatchableHandlerMapping mapping = getMapping(request);
+		if (mapping == null) {
+			return this.defaultMatcher.extractUriTemplateVariables(request);
+		}
+		RequestMatchResult result = mapping.match(request, this.pattern);
+		return result == null ? Collections.<String, String>emptyMap()
+				: result.extractUriTemplateVariables();
+	}
+
+	/**
+	 * @param method the method to set
+	 */
+	public void setMethod(HttpMethod method) {
+		this.method = method;
+	}
+
+	private class DefaultMatcher implements RequestMatcher, RequestVariablesExtractor {
+
+		private final UrlPathHelper pathHelper = new UrlPathHelper();
+
+		private final PathMatcher pathMatcher = new AntPathMatcher();
+
+		@Override
+		public boolean matches(HttpServletRequest request) {
+			String lookupPath = this.pathHelper.getLookupPathForRequest(request);
+			return matches(lookupPath);
+		}
+
+		private boolean matches(String lookupPath) {
+			return this.pathMatcher.match(MvcRequestMatcher.this.pattern, lookupPath);
+		}
+
+		@Override
+		public Map<String, String> extractUriTemplateVariables(
+				HttpServletRequest request) {
+			String lookupPath = this.pathHelper.getLookupPathForRequest(request);
+			if (matches(lookupPath)) {
+				return this.pathMatcher.extractUriTemplateVariables(
+						MvcRequestMatcher.this.pattern, lookupPath);
+			}
+			return Collections.emptyMap();
+		}
+	}
+}

+ 38 - 0
web/src/main/java/org/springframework/security/web/util/matcher/RequestVariablesExtractor.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012-2016 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
+ *
+ *      http://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.web.util.matcher;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * An interface for extracting URI variables from the {@link HttpServletRequest}.
+ *
+ * @author Rob Winch
+ * @since 4.1.1
+ */
+public interface RequestVariablesExtractor {
+
+	/**
+	 * Extract URL template variables from the request.
+	 *
+	 * @param request the HttpServletRequest to obtain a URL to extract the variables from
+	 * @return the URL variables or empty if no variables are found
+	 */
+	Map<String, String> extractUriTemplateVariables(HttpServletRequest request);
+}

+ 202 - 0
web/src/test/java/org/springframework/security/web/servlet/util/matcher/MvcRequestMatcherTests.java

@@ -0,0 +1,202 @@
+/*
+ * Copyright 2012-2016 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
+ *
+ *      http://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.web.servlet.util.matcher;
+
+import java.util.Collections;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
+import org.springframework.web.servlet.handler.MatchableHandlerMapping;
+import org.springframework.web.servlet.handler.RequestMatchResult;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Rob Winch
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class MvcRequestMatcherTests {
+	@Mock
+	HandlerMappingIntrospector introspector;
+	@Mock
+	MatchableHandlerMapping mapping;
+	@Mock
+	RequestMatchResult result;
+	@Captor
+	ArgumentCaptor<String> pattern;
+	MockHttpServletRequest request;
+
+	MvcRequestMatcher matcher;
+
+	@Before
+	public void setup() throws Exception {
+		this.request = new MockHttpServletRequest();
+		this.request.setMethod("GET");
+		this.request.setServletPath("/path");
+		this.matcher = new MvcRequestMatcher(this.introspector, "/path");
+	}
+
+	@Test
+	public void extractUriTemplateVariablesSuccess() throws Exception {
+		when(this.result.extractUriTemplateVariables())
+				.thenReturn(Collections.singletonMap("p", "path"));
+		when(this.introspector.getMatchableHandlerMapping(this.request))
+				.thenReturn(this.mapping);
+		when(this.mapping.match(eq(this.request), this.pattern.capture()))
+				.thenReturn(this.result);
+
+		this.matcher = new MvcRequestMatcher(this.introspector, "/{p}");
+		when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
+
+		assertThat(this.matcher.extractUriTemplateVariables(this.request))
+				.containsEntry("p", "path");
+	}
+
+	@Test
+	public void extractUriTemplateVariablesFail() throws Exception {
+		when(this.result.extractUriTemplateVariables())
+				.thenReturn(Collections.<String, String>emptyMap());
+		when(this.introspector.getMatchableHandlerMapping(this.request))
+				.thenReturn(this.mapping);
+		when(this.mapping.match(eq(this.request), this.pattern.capture()))
+				.thenReturn(this.result);
+
+		assertThat(this.matcher.extractUriTemplateVariables(this.request)).isEmpty();
+	}
+
+	@Test
+	public void extractUriTemplateVariablesDefaultSuccess() throws Exception {
+		this.matcher = new MvcRequestMatcher(this.introspector, "/{p}");
+		when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
+
+		assertThat(this.matcher.extractUriTemplateVariables(this.request))
+				.containsEntry("p", "path");
+	}
+
+	@Test
+	public void extractUriTemplateVariablesDefaultFail() throws Exception {
+		this.matcher = new MvcRequestMatcher(this.introspector, "/nomatch/{p}");
+		when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
+
+		assertThat(this.matcher.extractUriTemplateVariables(this.request)).isEmpty();
+	}
+
+	@Test
+	public void matchesPathOnlyTrue() throws Exception {
+		when(this.introspector.getMatchableHandlerMapping(this.request))
+				.thenReturn(this.mapping);
+		when(this.mapping.match(eq(this.request), this.pattern.capture()))
+				.thenReturn(this.result);
+
+		assertThat(this.matcher.matches(this.request)).isTrue();
+		assertThat(this.pattern.getValue()).isEqualTo("/path");
+	}
+
+	@Test
+	public void matchesDefaultMatches() throws Exception {
+		when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
+
+		assertThat(this.matcher.matches(this.request)).isTrue();
+	}
+
+	@Test
+	public void matchesDefaultDoesNotMatch() throws Exception {
+		this.request.setServletPath("/other");
+		when(this.introspector.getMatchableHandlerMapping(this.request)).thenReturn(null);
+
+		assertThat(this.matcher.matches(this.request)).isFalse();
+	}
+
+	@Test
+	public void matchesPathOnlyFalse() throws Exception {
+		when(this.introspector.getMatchableHandlerMapping(this.request))
+				.thenReturn(this.mapping);
+
+		assertThat(this.matcher.matches(this.request)).isFalse();
+	}
+
+	@Test
+	public void matchesMethodAndPathTrue() throws Exception {
+		this.matcher.setMethod(HttpMethod.GET);
+		when(this.introspector.getMatchableHandlerMapping(this.request))
+				.thenReturn(this.mapping);
+		when(this.mapping.match(eq(this.request), this.pattern.capture()))
+				.thenReturn(this.result);
+
+		assertThat(this.matcher.matches(this.request)).isTrue();
+		assertThat(this.pattern.getValue()).isEqualTo("/path");
+	}
+
+	@Test
+	public void matchesMethodAndPathFalseMethod() throws Exception {
+		this.matcher.setMethod(HttpMethod.POST);
+
+		assertThat(this.matcher.matches(this.request)).isFalse();
+		// method compare should be done first since faster
+		verifyZeroInteractions(this.introspector);
+	}
+
+	/**
+	 * Malicious users can specify any HTTP Method to create a stacktrace and try to
+	 * expose useful information about the system. We should ensure we ignore invalid HTTP
+	 * methods.
+	 * @throws Exception if an error occurs
+	 */
+	@Test
+	public void matchesInvalidMethodOnRequest() throws Exception {
+		this.matcher.setMethod(HttpMethod.GET);
+		this.request.setMethod("invalid");
+
+		assertThat(this.matcher.matches(this.request)).isFalse();
+		// method compare should be done first since faster
+		verifyZeroInteractions(this.introspector);
+	}
+
+	@Test
+	public void matchesMethodAndPathFalsePath() throws Exception {
+		this.matcher.setMethod(HttpMethod.GET);
+		when(this.introspector.getMatchableHandlerMapping(this.request))
+				.thenReturn(this.mapping);
+
+		assertThat(this.matcher.matches(this.request)).isFalse();
+	}
+
+	@Test
+	public void matchesGetMatchableHandlerMappingNull() throws Exception {
+		assertThat(this.matcher.matches(this.request)).isTrue();
+	}
+
+	@Test
+	public void matchesGetMatchableHandlerMappingThrows() throws Exception {
+		when(this.introspector.getMatchableHandlerMapping(this.request)).thenThrow(
+				new HttpRequestMethodNotSupportedException(this.request.getMethod()));
+		assertThat(this.matcher.matches(this.request)).isTrue();
+	}
+}