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;
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) {

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -17,8 +17,11 @@
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.lang.Nullable;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
@ -28,12 +31,23 @@ 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);
}
@Nullable
@Override
public Class<?> getObjectType() {
return HandlerMappingIntrospector.class;
}
/*

View File

@ -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);

View File

@ -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

View File

@ -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"() {

View File

@ -251,6 +251,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'

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`.
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.