mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 01:02:14 +00:00
Lookup HandlerMappingIntrospector from Bean
This commit is contained in:
parent
ef9cd76607
commit
d664ff2e26
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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"() {
|
||||||
|
@ -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'
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user