MvcRequestMatcher servletPath Polish / XML Config

Fixes gh-4014
This commit is contained in:
Joe Grandja 2016-07-22 10:24:12 -04:00 committed by Rob Winch
parent 3befb1c8a6
commit e080905a79
12 changed files with 345 additions and 179 deletions

View File

@ -15,15 +15,6 @@
*/
package org.springframework.security.config.annotation.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@ -34,9 +25,12 @@ 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.util.ClassUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A base class for registering {@link RequestMatcher}'s. For example, it might allow for
* specifying which {@link RequestMatcher} require a certain level of authorization.
@ -171,12 +165,9 @@ public abstract class AbstractRequestMatcherRegistry<C> {
List<MvcRequestMatcher> matchers = new ArrayList<MvcRequestMatcher>(
mvcPatterns.length);
for (String mvcPattern : mvcPatterns) {
MvcRequestMatcher matcher;
if(isServlet30) {
matcher = new ServletPathValidatingtMvcRequestMatcher(introspector, mvcPattern);
MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
if (isServlet30) {
opp.postProcess(matcher);
} else {
matcher = new MvcRequestMatcher(introspector, mvcPattern);
}
if (method != null) {
matcher.setMethod(method);
@ -316,48 +307,4 @@ public abstract class AbstractRequestMatcherRegistry<C> {
}
}
static class ServletPathValidatingtMvcRequestMatcher extends MvcRequestMatcher implements SmartInitializingSingleton, ServletContextAware {
private ServletContext servletContext;
/**
* @param introspector
* @param pattern
*/
public ServletPathValidatingtMvcRequestMatcher(HandlerMappingIntrospector introspector,
String pattern) {
super(introspector, pattern);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
@Override
public void afterSingletonsInstantiated() {
if(getServletPath() != null) {
return;
}
Collection<? extends ServletRegistration> registrations = servletContext.getServletRegistrations().values();
for(ServletRegistration registration : registrations) {
Collection<String> mappings = registration.getMappings();
for(String mapping : mappings) {
if(mapping.startsWith("/") && mapping.length() > 1) {
throw new IllegalStateException(
"servletPath must not be null for mvcPattern \"" + getMvcPattern()
+ "\" when providing a servlet mapping of "
+ mapping + " for servlet "
+ registration.getClassName());
}
}
}
}
/* (non-Javadoc)
* @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
}

View File

@ -1351,7 +1351,7 @@
'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are
2 or more HttpServlet's registered in the ServletContext that have mappings starting with
'/' and are different; 2) The pattern starts with the same value of a registered
HttpServlet path, excluding the default (root) HttpServlet '/'
HttpServlet path, excluding the default (root) HttpServlet '/'.
</xs:documentation>
</xs:annotation>
</xs:attribute>

View File

@ -381,6 +381,9 @@ intercept-url.attlist &=
intercept-url.attlist &=
## Used to specify that a URL must be accessed over http or https, or that there is no preference. The value should be "http", "https" or "any", respectively.
attribute requires-channel {xsd:token}?
intercept-url.attlist &=
## The path to the servlet. This attribute is only applicable when 'request-matcher' is 'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are 2 or more HttpServlet's registered in the ServletContext that have mappings starting with '/' and are different; 2) The pattern starts with the same value of a registered HttpServlet path, excluding the default (root) HttpServlet '/'.
attribute servlet-path {xsd:token}?
logout =
## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic.

View File

@ -1345,6 +1345,16 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="servlet-path" type="xs:token">
<xs:annotation>
<xs:documentation>The path to the servlet. This attribute is only applicable when 'request-matcher' is
'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are
2 or more HttpServlet's registered in the ServletContext that have mappings starting with
'/' and are different; 2) The pattern starts with the same value of a registered
HttpServlet path, excluding the default (root) HttpServlet '/'.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="logout.attlist">

View File

@ -1,26 +1,25 @@
package org.springframework.security.config
import groovy.xml.MarkupBuilder
import org.mockito.Mockito;
import org.springframework.context.support.AbstractXmlApplicationContext
import org.mockito.Mockito
import org.springframework.context.ApplicationListener
import org.springframework.context.support.AbstractRefreshableApplicationContext
import org.springframework.mock.web.MockServletContext
import org.springframework.security.CollectingAppListener
import org.springframework.security.config.util.InMemoryXmlApplicationContext
import org.springframework.security.config.util.InMemoryXmlWebApplicationContext
import org.springframework.security.core.context.SecurityContextHolder
import spock.lang.Specification
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML
import org.springframework.context.ApplicationListener
import org.springframework.context.ApplicationEvent
import org.springframework.security.authentication.event.AbstractAuthenticationEvent
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent
import org.springframework.security.access.event.AbstractAuthorizationEvent
import org.springframework.security.CollectingAppListener
import javax.servlet.ServletContext
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML
/**
*
* @author Luke Taylor
*/
abstract class AbstractXmlConfigTests extends Specification {
AbstractXmlApplicationContext appContext;
AbstractRefreshableApplicationContext appContext;
Writer writer;
MarkupBuilder xml;
ApplicationListener appListener;
@ -81,4 +80,27 @@ abstract class AbstractXmlConfigTests extends Specification {
appContext = new InMemoryXmlApplicationContext(writer.toString() + extraXml);
appContext.addApplicationListener(appListener);
}
def createWebAppContext() {
createWebAppContext(AUTH_PROVIDER_XML);
}
def createWebAppContext(ServletContext servletContext) {
createWebAppContext(AUTH_PROVIDER_XML, servletContext);
}
def createWebAppContext(String extraXml) {
createWebAppContext(extraXml, null);
}
def createWebAppContext(String extraXml, ServletContext servletContext) {
appContext = new InMemoryXmlWebApplicationContext(writer.toString() + extraXml);
appContext.addApplicationListener(appListener);
if (servletContext != null) {
appContext.setServletContext(servletContext);
} else {
appContext.setServletContext(new MockServletContext());
}
appContext.refresh();
}
}

View File

@ -15,13 +15,11 @@
*/
package org.springframework.security.config.doc
import groovy.util.slurpersupport.GPathResult;
import groovy.util.slurpersupport.NodeChild
import groovy.util.slurpersupport.GPathResult
import spock.lang.*
import org.springframework.security.config.http.SecurityFilters
import spock.lang.*
/**
* Tests to ensure that the xsd is properly documented.
*
@ -29,7 +27,15 @@ import spock.lang.*
*/
class XsdDocumentedTests extends Specification {
def ignoredIds = ['nsa-any-user-service','nsa-any-user-service-parents','nsa-authentication','nsa-websocket-security','nsa-ldap','nsa-method-security','nsa-web']
def ignoredIds = [
'nsa-any-user-service',
'nsa-any-user-service-parents',
'nsa-authentication',
'nsa-websocket-security',
'nsa-ldap',
'nsa-method-security',
'nsa-web'
]
@Shared def reference = new File('../docs/manual/src/docs/asciidoc/index.adoc')
@Shared File schema31xDocument = new File('src/main/resources/org/springframework/security/config/spring-security-3.1.xsd')
@ -52,32 +58,53 @@ class XsdDocumentedTests extends Specification {
def 'SEC-2139: named-security-filter are all defined and ordered properly'() {
setup:
def expectedFilters = (EnumSet.allOf(SecurityFilters) as List).sort { it.order }
def expectedFilters = (EnumSet.allOf(SecurityFilters) as List).sort { it.order }
when:
def nsf = schemaRootElement.simpleType.find { it.@name == 'named-security-filter' }
def nsfValues = nsf.children().children().collect { c ->
Enum.valueOf(SecurityFilters, c.@value.toString())
}
def nsf = schemaRootElement.simpleType.find { it.@name == 'named-security-filter' }
def nsfValues = nsf.children().children().collect { c ->
Enum.valueOf(SecurityFilters, c.@value.toString())
}
then:
expectedFilters == nsfValues
expectedFilters == nsfValues
}
def 'SEC-2139: 3.1.x named-security-filter are all defined and ordered properly'() {
setup:
def expectedFilters = ["FIRST", "CHANNEL_FILTER", "SECURITY_CONTEXT_FILTER", "CONCURRENT_SESSION_FILTER", "LOGOUT_FILTER", "X509_FILTER",
"PRE_AUTH_FILTER", "CAS_FILTER", "FORM_LOGIN_FILTER", "OPENID_FILTER", "LOGIN_PAGE_FILTER", "DIGEST_AUTH_FILTER","BASIC_AUTH_FILTER",
"REQUEST_CACHE_FILTER", "SERVLET_API_SUPPORT_FILTER", "JAAS_API_SUPPORT_FILTER", "REMEMBER_ME_FILTER", "ANONYMOUS_FILTER",
"SESSION_MANAGEMENT_FILTER", "EXCEPTION_TRANSLATION_FILTER", "FILTER_SECURITY_INTERCEPTOR", "SWITCH_USER_FILTER", "LAST"].collect {
Enum.valueOf(SecurityFilters, it)
}
def schema31xRootElement = new XmlSlurper().parse(schema31xDocument)
def expectedFilters = [
"FIRST",
"CHANNEL_FILTER",
"SECURITY_CONTEXT_FILTER",
"CONCURRENT_SESSION_FILTER",
"LOGOUT_FILTER",
"X509_FILTER",
"PRE_AUTH_FILTER",
"CAS_FILTER",
"FORM_LOGIN_FILTER",
"OPENID_FILTER",
"LOGIN_PAGE_FILTER",
"DIGEST_AUTH_FILTER",
"BASIC_AUTH_FILTER",
"REQUEST_CACHE_FILTER",
"SERVLET_API_SUPPORT_FILTER",
"JAAS_API_SUPPORT_FILTER",
"REMEMBER_ME_FILTER",
"ANONYMOUS_FILTER",
"SESSION_MANAGEMENT_FILTER",
"EXCEPTION_TRANSLATION_FILTER",
"FILTER_SECURITY_INTERCEPTOR",
"SWITCH_USER_FILTER",
"LAST"
].collect {
Enum.valueOf(SecurityFilters, it)
}
def schema31xRootElement = new XmlSlurper().parse(schema31xDocument)
when:
def nsf = schema31xRootElement.simpleType.find { it.@name == 'named-security-filter' }
def nsfValues = nsf.children().children().collect { c ->
Enum.valueOf(SecurityFilters, c.@value.toString())
}
def nsf = schema31xRootElement.simpleType.find { it.@name == 'named-security-filter' }
def nsfValues = nsf.children().children().collect { c ->
Enum.valueOf(SecurityFilters, c.@value.toString())
}
then:
expectedFilters == nsfValues
expectedFilters == nsfValues
}
/**
@ -100,22 +127,22 @@ class XsdDocumentedTests extends Specification {
*/
def 'the entire schema is included in the appendix documentation'() {
setup: 'get all the documented ids and the expected ids'
def documentedIds = []
reference.eachLine { line ->
if(line.matches("\\[\\[(nsa-.*)\\]\\]")) {
documentedIds.add(line.substring(2,line.length() - 2))
}
def documentedIds = []
reference.eachLine { line ->
if(line.matches("\\[\\[(nsa-.*)\\]\\]")) {
documentedIds.add(line.substring(2,line.length() - 2))
}
}
when: 'the schema is compared to the appendix documentation'
def expectedIds = [] as Set
elementNameToElement*.value*.ids*.each { expectedIds.addAll it }
documentedIds.removeAll ignoredIds
expectedIds.removeAll ignoredIds
def undocumentedIds = (expectedIds - documentedIds)
def shouldNotBeDocumented = (documentedIds - expectedIds)
def expectedIds = [] as Set
elementNameToElement*.value*.ids*.each { expectedIds.addAll it }
documentedIds.removeAll ignoredIds
expectedIds.removeAll ignoredIds
def undocumentedIds = (expectedIds - documentedIds)
def shouldNotBeDocumented = (documentedIds - expectedIds)
then: 'all the elements and attributes are documented'
shouldNotBeDocumented.empty
undocumentedIds.empty
shouldNotBeDocumented.empty
undocumentedIds.empty
}
/**
@ -125,55 +152,55 @@ class XsdDocumentedTests extends Specification {
*/
def 'validate parents and children are linked in the appendix documentation'() {
when: "get all the links for each element's children and parents"
def docAttrNameToChildren = [:]
def docAttrNameToParents = [:]
def docAttrNameToChildren = [:]
def docAttrNameToParents = [:]
def currentDocAttrNameToElmt
def docAttrName
def currentDocAttrNameToElmt
def docAttrName
reference.eachLine { line ->
if(line.matches('^\\[\\[.*\\]\\]$')) {
def id = line.substring(2,line.length() - 2)
if(id.endsWith("-children")) {
docAttrName = id.substring(0,id.length() - 9)
currentDocAttrNameToElmt = docAttrNameToChildren
} else if(id.endsWith("-parents")) {
docAttrName = id.substring(0,id.length() - 8)
currentDocAttrNameToElmt = docAttrNameToParents
} else if(docAttrName && !id.startsWith(docAttrName)) {
currentDocAttrNameToElmt = null
docAttrName = null
}
}
if(docAttrName) {
def expression = '^\\* <<(nsa-.*),.*>>$'
if(line.matches(expression)) {
String elmtId = line.replaceAll(expression, '$1')
currentDocAttrNameToElmt.get(docAttrName, []).add(elmtId)
}
reference.eachLine { line ->
if(line.matches('^\\[\\[.*\\]\\]$')) {
def id = line.substring(2,line.length() - 2)
if(id.endsWith("-children")) {
docAttrName = id.substring(0,id.length() - 9)
currentDocAttrNameToElmt = docAttrNameToChildren
} else if(id.endsWith("-parents")) {
docAttrName = id.substring(0,id.length() - 8)
currentDocAttrNameToElmt = docAttrNameToParents
} else if(docAttrName && !id.startsWith(docAttrName)) {
currentDocAttrNameToElmt = null
docAttrName = null
}
}
def schemaAttrNameToParents = [:]
def schemaAttrNameToChildren = [:]
elementNameToElement.each { entry ->
def key = 'nsa-'+entry.key
if(ignoredIds.contains(key)) {
return
}
def parentIds = entry.value.allParentElmts.values()*.id.findAll { !ignoredIds.contains(it) }.sort()
if(parentIds) {
schemaAttrNameToParents.put(key,parentIds)
}
def childIds = entry.value.allChildElmts.values()*.id.findAll { !ignoredIds.contains(it) }.sort()
if(childIds) {
schemaAttrNameToChildren.put(key,childIds)
if(docAttrName) {
def expression = '^\\* <<(nsa-.*),.*>>$'
if(line.matches(expression)) {
String elmtId = line.replaceAll(expression, '$1')
currentDocAttrNameToElmt.get(docAttrName, []).add(elmtId)
}
}
}
def schemaAttrNameToParents = [:]
def schemaAttrNameToChildren = [:]
elementNameToElement.each { entry ->
def key = 'nsa-'+entry.key
if(ignoredIds.contains(key)) {
return
}
def parentIds = entry.value.allParentElmts.values()*.id.findAll { !ignoredIds.contains(it) }.sort()
if(parentIds) {
schemaAttrNameToParents.put(key,parentIds)
}
def childIds = entry.value.allChildElmts.values()*.id.findAll { !ignoredIds.contains(it) }.sort()
if(childIds) {
schemaAttrNameToChildren.put(key,childIds)
}
}
then: "the expected parents and children are all documented"
schemaAttrNameToChildren.sort() == docAttrNameToChildren.sort()
schemaAttrNameToParents.sort() == docAttrNameToParents.sort()
schemaAttrNameToChildren.sort() == docAttrNameToChildren.sort()
schemaAttrNameToParents.sort() == docAttrNameToParents.sort()
}
/**

View File

@ -15,17 +15,25 @@
*/
package org.springframework.security.config.http
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
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.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
import javax.servlet.ServletContext
import javax.servlet.ServletRegistration
import javax.servlet.http.HttpServletResponse
import static org.mockito.Mockito.*
/**
*
* @author Rob Winch
@ -199,6 +207,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
def "intercept-url supports mvc matchers"() {
setup:
MockServletContext servletContext = mockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
@ -209,7 +218,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
bean('pathController',PathController)
xml.'mvc:annotation-driven'()
createAppContext()
createWebAppContext(servletContext)
when:
request.servletPath = "/path"
springSecurityFilterChain.doFilter(request, response, chain)
@ -235,6 +244,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
def "intercept-url mvc supports path variables"() {
setup:
MockServletContext servletContext = mockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
@ -242,7 +252,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
'http-basic'()
'intercept-url'(pattern: '/user/{un}/**', access: "#un == 'user'")
}
createAppContext()
createWebAppContext(servletContext)
when: 'user can access'
request.servletPath = '/user/user/abc'
springSecurityFilterChain.doFilter(request,response,chain)
@ -266,6 +276,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
def "intercept-url mvc matchers with servlet path"() {
setup:
MockServletContext servletContext = mockServletContext("/spring");
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
MockHttpServletResponse response = new MockHttpServletResponse()
MockFilterChain chain = new MockFilterChain()
@ -275,7 +286,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
}
bean('pathController',PathController)
xml.'mvc:annotation-driven'()
createAppContext()
createWebAppContext(servletContext)
when:
request.servletPath = "/spring"
request.requestURI = "/spring/path"
@ -302,6 +313,30 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
response.status == HttpServletResponse.SC_UNAUTHORIZED
}
def "intercept-url mvc matchers servlet path required"() {
when:
MockServletContext servletContext = mockServletContext("/spring");
xml.http('request-matcher':'mvc') {
'http-basic'()
'intercept-url'(pattern: '/path', access: "denyAll")
}
createWebAppContext(servletContext)
then:
thrown(BeanCreationException)
}
def "intercept-url mvc matchers servlet path NOT required"() {
when:
MockServletContext servletContext = mockServletContext();
xml.http('request-matcher':'mvc') {
'http-basic'()
'intercept-url'(pattern: '/path', access: "denyAll")
}
createWebAppContext(servletContext)
then:
noExceptionThrown()
}
def "intercept-url ant matcher with servlet path fails"() {
when:
xml.http('request-matcher':'ant') {
@ -352,6 +387,24 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
}
}
private ServletContext mockServletContext() {
return mockServletContext("/");
}
private ServletContext mockServletContext(String servletPath) {
MockServletContext servletContext = spy(new MockServletContext());
final ServletRegistration registration = mock(ServletRegistration.class);
when(registration.getMappings()).thenReturn(Collections.singleton(servletPath));
Answer<Map<String, ? extends ServletRegistration>> answer = new Answer<Map<String, ? extends ServletRegistration>>() {
@Override
public Map<String, ? extends ServletRegistration> answer(InvocationOnMock invocation) throws Throwable {
return Collections.<String, ServletRegistration>singletonMap("spring", registration);
}
};
when(servletContext.getServletRegistrations()).thenAnswer(answer);
return servletContext;
}
def login(MockHttpServletRequest request, String username, String password) {
String toEncode = username + ':' + password
request.addHeader('Authorization','Basic ' + new String(Base64.encode(toEncode.getBytes('UTF-8'))))

View File

@ -16,14 +16,6 @@
package org.springframework.security.config.annotation.web;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import javax.servlet.Registration;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -31,14 +23,17 @@ import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.ServletPathValidatingtMvcRequestMatcher;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import static org.mockito.Mockito.*;
/**
* @author Rob Winch
@ -49,34 +44,34 @@ public class AbstractRequestMatcherRegistryTests {
@Mock
HandlerMappingIntrospector introspector;
ServletPathValidatingtMvcRequestMatcher matcher;
MvcRequestMatcher matcher;
ServletContext servletContext;
@Before
public void setup() {
servletContext = spy(new MockServletContext());
matcher = new ServletPathValidatingtMvcRequestMatcher(introspector, "/foo");
matcher = new MvcRequestMatcher(introspector, "/foo");
matcher.setServletContext(servletContext);
}
@Test(expected = IllegalStateException.class)
public void servletPathValidatingtMvcRequestMatcherAfterSingletonsIntantiatedFailsWithSpringServlet() {
public void servletPathValidatingMvcRequestMatcherAfterPropertiesSetFailsWithSpringServlet() throws Exception {
setMappings("/spring");
matcher.afterSingletonsInstantiated();
matcher.afterPropertiesSet();
}
@Test
public void servletPathValidatingtMvcRequestMatcherAfterSingletonsIntantiatedWithSpringServlet() {
public void servletPathValidatingMvcRequestMatcherAfterPropertiesSetWithSpringServlet() throws Exception {
matcher.setServletPath("/spring");
setMappings("/spring");
matcher.afterSingletonsInstantiated();
matcher.afterPropertiesSet();
}
@Test
public void servletPathValidatingtMvcRequestMatcherAfterSingletonsIntantiatedDefaultServlet() {
public void servletPathValidatingMvcRequestMatcherAfterPropertiesSetDefaultServlet() throws Exception {
setMappings("/");
matcher.afterSingletonsInstantiated();
matcher.afterPropertiesSet();
}
private void setMappings(String... mappings) {

View File

@ -27,6 +27,7 @@ import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -449,7 +450,7 @@ public class AuthorizeRequestsTests {
}
}
@Test(expected = IllegalStateException.class)
@Test(expected = BeanCreationException.class)
public void mvcMatcherServletPathRequired() throws Exception {
final ServletRegistration registration = mock(ServletRegistration.class);
when(registration.getMappings()).thenReturn(Collections.singleton("/spring"));
@ -502,4 +503,4 @@ public class AuthorizeRequestsTests {
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
}
}

View File

@ -25,7 +25,7 @@ import org.springframework.security.util.InMemoryResource;
* @author Luke Taylor
*/
public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext {
private static final String BEANS_OPENING = "<b:beans xmlns='http://www.springframework.org/schema/security'\n"
static final String BEANS_OPENING = "<b:beans xmlns='http://www.springframework.org/schema/security'\n"
+ " xmlns:context='http://www.springframework.org/schema/context'\n"
+ " xmlns:b='http://www.springframework.org/schema/beans'\n"
+ " xmlns:aop='http://www.springframework.org/schema/aop'\n"
@ -38,16 +38,18 @@ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext
+ "http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd\n"
+ "http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd\n"
+ "http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-";
private static final String BEANS_CLOSE = "</b:beans>\n";
static final String BEANS_CLOSE = "</b:beans>\n";
static final String SPRING_SECURITY_VERSION = "4.1";
Resource inMemoryXml;
public InMemoryXmlApplicationContext(String xml) {
this(xml, "4.1", null);
this(xml, SPRING_SECURITY_VERSION, null);
}
public InMemoryXmlApplicationContext(String xml, ApplicationContext parent) {
this(xml, "4.1", parent);
this(xml, SPRING_SECURITY_VERSION, parent);
}
public InMemoryXmlApplicationContext(String xml, String secVersion,

View File

@ -0,0 +1,60 @@
/*
* 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.util;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.security.util.InMemoryResource;
import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext;
import java.io.IOException;
import static org.springframework.security.config.util.InMemoryXmlApplicationContext.BEANS_CLOSE;
import static org.springframework.security.config.util.InMemoryXmlApplicationContext.BEANS_OPENING;
import static org.springframework.security.config.util.InMemoryXmlApplicationContext.SPRING_SECURITY_VERSION;
/**
* @author Joe Grandja
*/
public class InMemoryXmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
private Resource inMemoryXml;
public InMemoryXmlWebApplicationContext(String xml) {
this(xml, SPRING_SECURITY_VERSION, null);
}
public InMemoryXmlWebApplicationContext(String xml, ApplicationContext parent) {
this(xml, SPRING_SECURITY_VERSION, parent);
}
public InMemoryXmlWebApplicationContext(String xml, String secVersion,
ApplicationContext parent) {
String fullXml = BEANS_OPENING + secVersion + ".xsd'>\n" + xml + BEANS_CLOSE;
inMemoryXml = new InMemoryResource(fullXml);
setAllowBeanDefinitionOverriding(true);
setParent(parent);
}
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new Resource[] { inMemoryXml });
}
}

View File

@ -16,17 +16,24 @@
package org.springframework.security.web.servlet.util.matcher;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
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.ClassUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.ServletContextAware;
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;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@ -45,13 +52,18 @@ import java.util.Map;
* @since 4.1.1
*/
public class MvcRequestMatcher
implements RequestMatcher, RequestVariablesExtractor {
implements RequestMatcher, RequestVariablesExtractor, InitializingBean, ServletContextAware {
private static final boolean isServlet30 = ClassUtils.isPresent(
"javax.servlet.ServletRegistration", MvcRequestMatcher.class.getClassLoader());
private final DefaultMatcher defaultMatcher = new DefaultMatcher();
private final HandlerMappingIntrospector introspector;
private final String pattern;
private HttpMethod method;
private String servletPath;
private ServletContext servletContext;
public MvcRequestMatcher(HandlerMappingIntrospector introspector, String pattern) {
this.introspector = introspector;
@ -100,6 +112,40 @@ public class MvcRequestMatcher
: result.extractUriTemplateVariables();
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
// servletPath is required when at least one registered Servlet
// is mapped to a path other than the default (root) path '/'
if (this.servletContext == null || !isServlet30) {
return;
}
if (this.getServletPath() != null) {
return;
}
for (ServletRegistration registration : this.servletContext.getServletRegistrations().values()) {
for (String mapping : registration.getMappings()) {
if (mapping.startsWith("/") && mapping.length() > 1) {
throw new IllegalStateException(
"servletPath must not be null for mvcPattern \"" + this.getMvcPattern()
+ "\" when providing a servlet mapping of "
+ mapping + " for servlet "
+ registration.getClassName());
}
}
}
}
/* (non-Javadoc)
* @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* @param method the method to set
*/