Lookup HandlerMappingIntrospector from Bean

This commit is contained in:
Rob Winch 2017-09-27 10:31:16 -05:00
parent ef9cd76607
commit d664ff2e26
9 changed files with 112 additions and 50 deletions

View File

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

View File

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

View File

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

View File

@ -17,8 +17,11 @@
package org.springframework.security.config.http; package org.springframework.security.config.http;
import org.springframework.beans.BeansException; 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.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/** /**
@ -28,12 +31,23 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
* @author Rob Winch * @author Rob Winch
* @since 4.1.1 * @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; private ApplicationContext context;
HandlerMappingIntrospector createHandlerMappingIntrospector() { public HandlerMappingIntrospector getObject() {
return new HandlerMappingIntrospector(this.context); 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);
}
@Nullable
@Override
public Class<?> getObjectType() {
return HandlerMappingIntrospector.class;
} }
/* /*

View File

@ -37,8 +37,7 @@ public enum MatcherType {
ant(AntPathRequestMatcher.class), regex(RegexRequestMatcher.class), ciRegex( ant(AntPathRequestMatcher.class), regex(RegexRequestMatcher.class), ciRegex(
RegexRequestMatcher.class), mvc(MvcRequestMatcher.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_BEAN_NAME = "mvcHandlerMappingIntrospector";
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 static final String ATT_MATCHER_TYPE = "request-matcher";
@ -61,18 +60,7 @@ public enum MatcherType {
.rootBeanDefinition(type); .rootBeanDefinition(type);
if (this == mvc) { if (this == mvc) {
if (!pc.getRegistry().isBeanNameInUse(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) { matcherBldr.addConstructorArgValue(new RootBeanDefinition(HandlerMappingIntrospectorFactoryBean.class));
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(path);

View File

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

View File

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

View File

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

View File

@ -7021,6 +7021,64 @@ NOTE: Spring Security provides the configuration using Spring MVC's https://docs
Spring Security provides deep integration with how Spring MVC matches on URLs with `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. 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] [NOTE]
==== ====
It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security. It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.