فهرست منبع

Lookup HandlerMappingIntrospector from Bean

Rob Winch 8 سال پیش
والد
کامیت
04d14fb6fe

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

@@ -15,6 +15,7 @@
  */
 package org.springframework.security.config.annotation.web;
 
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -42,6 +43,8 @@ import java.util.List;
  * @since 3.2
  */
 public abstract class AbstractRequestMatcherRegistry<C> {
+	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
+
 	private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;
 
 	private ApplicationContext context;
@@ -160,8 +163,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
 			String... mvcPatterns) {
 		boolean isServlet30 = ClassUtils.isPresent("javax.servlet.ServletRegistration", getClass().getClassLoader());
 		ObjectPostProcessor<Object> opp = this.context.getBean(ObjectPostProcessor.class);
-		HandlerMappingIntrospector introspector = new HandlerMappingIntrospector(
-				this.context);
+		if(!this.context.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 = this.context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
+			HandlerMappingIntrospector.class);
 		List<MvcRequestMatcher> matchers = new ArrayList<MvcRequestMatcher>(
 				mvcPatterns.length);
 		for (String mvcPattern : mvcPatterns) {

+ 9 - 3
config/src/main/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurer.java

@@ -15,6 +15,7 @@
  */
 package org.springframework.security.config.annotation.web.configurers;
 
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -98,7 +99,9 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>>
 		return null;
 	}
 
+
 	static class MvcCorsFilter {
+		private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
 		/**
 		 * This needs to be isolated into a separate class as Spring MVC is an optional
 		 * dependency and will potentially cause ClassLoading issues
@@ -106,9 +109,12 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>>
 		 * @return
 		 */
 		private static CorsFilter getMvcCorsFilter(ApplicationContext context) {
-			HandlerMappingIntrospector mappingIntrospector = new HandlerMappingIntrospector(
-					context);
+			if(!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
+				throw new NoSuchBeanDefinitionException(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, "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 mappingIntrospector = context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
 			return new CorsFilter(mappingIntrospector);
 		}
 	}
-}
+}

+ 3 - 6
config/src/main/java/org/springframework/security/config/http/CorsBeanDefinitionParser.java

@@ -15,17 +15,16 @@
  */
 package org.springframework.security.config.http;
 
-import org.w3c.dom.Element;
-
 import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
 import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.RootBeanDefinition;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.CorsFilter;
+import org.w3c.dom.Element;
 
 /**
  * Parser for the {@code CorsFilter}.
@@ -71,8 +70,6 @@ public class CorsBeanDefinitionParser {
 			return null;
 		}
 
-		BeanDefinitionBuilder configurationSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(HANDLER_MAPPING_INTROSPECTOR);
-		configurationSourceBldr.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
-		return configurationSourceBldr.getBeanDefinition();
+		return new RootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class);
 	}
 }

+ 19 - 3
config/src/main/java/org/springframework/security/config/http/HandlerMappingIntrospectorFactoryBean.java

@@ -17,6 +17,8 @@
 package org.springframework.security.config.http;
 
 import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@@ -28,12 +30,22 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
  * @author Rob Winch
  * @since 4.1.1
  */
-class HandlerMappingIntrospectorFactoryBean implements ApplicationContextAware {
+class HandlerMappingIntrospectorFactoryBean implements FactoryBean<HandlerMappingIntrospector>, ApplicationContextAware {
+	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
 
 	private ApplicationContext context;
 
-	HandlerMappingIntrospector createHandlerMappingIntrospector() {
-		return new HandlerMappingIntrospector(this.context);
+	public HandlerMappingIntrospector getObject() {
+		if(!this.context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
+			throw new NoSuchBeanDefinitionException(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, "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.");
+		}
+		return this.context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
+	}
+
+	@Override
+	public Class<?> getObjectType() {
+		return HandlerMappingIntrospector.class;
 	}
 
 	/*
@@ -48,4 +60,8 @@ class HandlerMappingIntrospectorFactoryBean implements ApplicationContextAware {
 		this.context = applicationContext;
 	}
 
+	@Override
+	public boolean isSingleton() {
+		return true;
+	}
 }

+ 2 - 14
config/src/main/java/org/springframework/security/config/http/MatcherType.java

@@ -37,8 +37,7 @@ public enum MatcherType {
 	ant(AntPathRequestMatcher.class), regex(RegexRequestMatcher.class), ciRegex(
 			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 HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
 
 	private static final String ATT_MATCHER_TYPE = "request-matcher";
 
@@ -61,18 +60,7 @@ public enum MatcherType {
 				.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(new RootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class));
 		}
 
 		matcherBldr.addConstructorArgValue(path);

+ 7 - 11
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CorsConfigurerTests.groovy

@@ -15,6 +15,9 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+
 import javax.servlet.http.HttpServletResponse
 
 import org.springframework.context.annotation.Bean
@@ -36,19 +39,12 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc
  */
 class CorsConfigurerTests extends BaseSpringSpec {
 
-	def "HandlerMappingIntrospector default"() {
-		setup:
-		loadConfig(DefaultCorsConfig)
+	def "No MVC throws meaningful error"() {
 		when:
-		addCors()
-		springSecurityFilterChain.doFilter(request,response,chain)
+		loadConfig(DefaultCorsConfig)
 		then:
-		responseHeaders == ['X-Content-Type-Options':'nosniff',
-			'X-Frame-Options':'DENY',
-			'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
-			'Expires' : '0',
-			'Pragma':'no-cache',
-			'X-XSS-Protection' : '1; mode=block']
+		BeanCreationException success = thrown()
+		success.message.contains("Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext")
 	}
 
 	@EnableWebSecurity

+ 6 - 11
config/src/test/groovy/org/springframework/security/config/http/HttpCorsConfigTests.groovy

@@ -12,6 +12,8 @@
  */
 package org.springframework.security.config.http
 
+import org.springframework.beans.factory.BeanCreationException
+
 import javax.servlet.http.HttpServletResponse
 
 import org.springframework.http.*
@@ -38,24 +40,17 @@ class HttpCorsConfigTests extends AbstractHttpConfigTests {
 		chain = new MockFilterChain()
 	}
 
-	def "HandlerMappingIntrospector default"() {
-		setup:
+	def "No MVC throws meaningful error"() {
+		when:
 		xml.http('entry-point-ref' : 'ep') {
 			'cors'()
 			'intercept-url'(pattern:'/**', access: 'authenticated')
 		}
 		bean('ep', Http403ForbiddenEntryPoint)
 		createAppContext()
-		when:
-		addCors()
-		springSecurityFilterChain.doFilter(request,response,chain)
 		then:
-		responseHeaders == ['X-Content-Type-Options':'nosniff',
-			'X-Frame-Options':'DENY',
-			'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
-			'Expires' : '0',
-			'Pragma':'no-cache',
-			'X-XSS-Protection' : '1; mode=block']
+		BeanCreationException success = thrown()
+		success.message.contains("Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext")
 	}
 
 	def "HandlerMappingIntrospector explicit"() {

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

@@ -252,6 +252,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
 			'http-basic'()
 			'intercept-url'(pattern: '/user/{un}/**', access: "#un == 'user'")
 		}
+		xml.'mvc:annotation-driven'()
 		createWebAppContext(servletContext)
 		when: 'user can access'
 		request.servletPath = '/user/user/abc'

+ 58 - 0
docs/manual/src/docs/asciidoc/index.adoc

@@ -6733,6 +6733,64 @@ NOTE: Spring Security provides the configuration using Spring MVC's http://docs.
 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.
 
+In order to use `MvcRequestMatcher` you must place the Spring Security Configuration in the same `ApplicationContext` as your `DispatcherServlet`.
+This is necessary because Spring Security's `MvcRequestMatcher` expects a `HandlerMappingIntrospector` bean with the name of `mvcHandlerMappingIntrospector` to be registered by your Spring MVC configuration that is used to perform the matching.
+
+For a `web.xml` this means that you should place your configuration in the `DispatcherServlet.xml`.
+
+[source,xml]
+----
+<listener>
+  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+</listener>
+
+<!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ -->
+<context-param>
+  <param-name>contextConfigLocation</param-name>
+  <param-value>/WEB-INF/spring/*.xml</param-value>
+</context-param>
+
+<servlet>
+  <servlet-name>spring</servlet-name>
+  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+  <!-- Load from the ContextLoaderListener -->
+  <init-param>
+    <param-name>contextConfigLocation</param-name>
+    <param-value></param-value>
+  </init-param>
+</servlet>
+
+<servlet-mapping>
+  <servlet-name>spring</servlet-name>
+  <url-pattern>/</url-pattern>
+</servlet-mapping>
+----
+
+Below `WebSecurityConfiguration` in placed in the ``DispatcherServlet``s `ApplicationContext`.
+
+[source,java]
+----
+public class SecurityInitializer extends
+    AbstractAnnotationConfigDispatcherServletInitializer {
+
+  @Override
+  protected Class<?>[] getRootConfigClasses() {
+    return null;
+  }
+
+  @Override
+  protected Class<?>[] getServletConfigClasses() {
+    return new Class[] { RootConfiguration.class,
+        WebMvcConfiguration.class };
+  }
+
+  @Override
+  protected String[] getServletMappings() {
+    return new String[] { "/" };
+  }
+}
+----
+
 [NOTE]
 ====
 It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.