mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-14 06:13:30 +00:00
Make sure default lower/upper case is respected for regex and ant paths when not set explicitly using the lowercase-comparisons attribute. Added much more comprehensive testing of HttpSecurityBeanDefinitionParser.
This commit is contained in:
parent
b9561cc4e0
commit
24caad5a67
@ -32,6 +32,7 @@ import org.springframework.security.securechannel.RetryWithHttpsEntryPoint;
|
|||||||
import org.springframework.security.ui.ExceptionTranslationFilter;
|
import org.springframework.security.ui.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.util.FilterChainProxy;
|
import org.springframework.security.util.FilterChainProxy;
|
||||||
import org.springframework.security.util.RegexUrlPathMatcher;
|
import org.springframework.security.util.RegexUrlPathMatcher;
|
||||||
|
import org.springframework.security.util.AntUrlPathMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
import org.springframework.util.xml.DomUtils;
|
||||||
@ -120,25 +121,39 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
|
FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
|
||||||
FilterInvocationDefinitionMap channelFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
|
FilterInvocationDefinitionMap channelFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
|
||||||
|
|
||||||
if (patternType.equals(OPT_PATH_TYPE_REGEX)) {
|
|
||||||
filterChainProxy.getPropertyValues().addPropertyValue("matcher", new RegexUrlPathMatcher());
|
|
||||||
interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
|
|
||||||
channelFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with lowercase conversion requests
|
// Deal with lowercase conversion requests
|
||||||
String lowercaseComparisons = element.getAttribute(ATT_LOWERCASE_COMPARISONS);
|
String lowercaseComparisons = element.getAttribute(ATT_LOWERCASE_COMPARISONS);
|
||||||
if (!StringUtils.hasText(lowercaseComparisons)) {
|
if (!StringUtils.hasText(lowercaseComparisons)) {
|
||||||
lowercaseComparisons = DEF_LOWERCASE_COMPARISONS;
|
lowercaseComparisons = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only change from the defaults if the attribute has been set
|
||||||
if ("true".equals(lowercaseComparisons)) {
|
if ("true".equals(lowercaseComparisons)) {
|
||||||
interceptorFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(true);
|
interceptorFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(true);
|
||||||
channelFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(true);
|
channelFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(true);
|
||||||
} else {
|
} else if ("false".equals(lowercaseComparisons)) {
|
||||||
interceptorFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(false);
|
interceptorFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(false);
|
||||||
channelFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(false);
|
channelFilterInvDefSource.setConvertUrlToLowercaseBeforeComparison(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (patternType.equals(OPT_PATH_TYPE_REGEX)) {
|
||||||
|
RegexUrlPathMatcher matcher = new RegexUrlPathMatcher();
|
||||||
|
|
||||||
|
if (lowercaseComparisons != null) {
|
||||||
|
matcher.setRequiresLowerCaseUrl("true".equals(lowercaseComparisons));
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
|
||||||
|
|
||||||
|
interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
|
||||||
|
channelFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
|
||||||
|
} else if (lowercaseComparisons != null) {
|
||||||
|
AntUrlPathMatcher matcher = new AntUrlPathMatcher();
|
||||||
|
matcher.setRequiresLowerCaseUrl("true".equals(lowercaseComparisons));
|
||||||
|
|
||||||
|
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
|
||||||
|
}
|
||||||
|
|
||||||
// Add servlet-api integration filter if required
|
// Add servlet-api integration filter if required
|
||||||
String provideServletApi = element.getAttribute(ATT_SERVLET_API_PROVISION);
|
String provideServletApi = element.getAttribute(ATT_SERVLET_API_PROVISION);
|
||||||
if (!StringUtils.hasText(provideServletApi)) {
|
if (!StringUtils.hasText(provideServletApi)) {
|
||||||
|
@ -82,5 +82,9 @@ public class OrderedFilterBeanDefinitionDecorator implements BeanDefinitionDecor
|
|||||||
public final void setOrder(int order) {
|
public final void setOrder(int order) {
|
||||||
this.order = new Integer(order);
|
this.order = new Integer(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBeanName() {
|
||||||
|
return beanName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package org.springframework.security.config;
|
package org.springframework.security.config;
|
||||||
|
|
||||||
import org.springframework.security.concurrent.ConcurrentSessionFilter;
|
|
||||||
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
||||||
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
||||||
|
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
|
||||||
|
import org.springframework.security.intercept.web.FilterInvocation;
|
||||||
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
||||||
import org.springframework.security.ui.ExceptionTranslationFilter;
|
import org.springframework.security.ui.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
|
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
|
||||||
@ -12,15 +13,19 @@ import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
|||||||
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
||||||
import org.springframework.security.util.FilterChainProxy;
|
import org.springframework.security.util.FilterChainProxy;
|
||||||
import org.springframework.security.util.PortMapperImpl;
|
import org.springframework.security.util.PortMapperImpl;
|
||||||
|
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||||
import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
|
import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
|
||||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
import org.springframework.security.providers.anonymous.AnonymousProcessingFilter;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.security.MockFilterChain;
|
||||||
|
import org.springframework.security.ConfigAttributeDefinition;
|
||||||
|
import org.springframework.security.SecurityConfig;
|
||||||
|
import org.springframework.context.support.AbstractXmlApplicationContext;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -30,47 +35,39 @@ import java.util.List;
|
|||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class HttpSecurityBeanDefinitionParserTests {
|
public class HttpSecurityBeanDefinitionParserTests {
|
||||||
private static ClassPathXmlApplicationContext appContext;
|
private AbstractXmlApplicationContext appContext;
|
||||||
|
private static final String AUTH_PROVIDER_XML =
|
||||||
|
" <authentication-provider>" +
|
||||||
|
" <user-service>" +
|
||||||
|
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
||||||
|
" <user name='bill' password='billspassword' authorities='ROLE_A,ROLE_B,AUTH_OTHER' />" +
|
||||||
|
" </user-service>" +
|
||||||
|
" </authentication-provider>";
|
||||||
|
|
||||||
@BeforeClass
|
@After
|
||||||
public static void loadContext() {
|
public void closeAppContext() {
|
||||||
try {
|
|
||||||
appContext = new ClassPathXmlApplicationContext("org/springframework/security/config/http-security.xml");
|
|
||||||
} catch (BeansException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void closeAppContext() {
|
|
||||||
if (appContext != null) {
|
if (appContext != null) {
|
||||||
appContext.close();
|
appContext.close();
|
||||||
|
appContext = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filterChainProxyShouldReturnEmptyFilterListForUnprotectedUrl() {
|
public void httpAutoConfigSetsUpCorrectFilterList() {
|
||||||
FilterChainProxy filterChainProxy =
|
setContext("<http auto-config='true'/>" + AUTH_PROVIDER_XML);
|
||||||
(FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
|
||||||
|
|
||||||
List filters = filterChainProxy.getFilters("/unprotected");
|
FilterChainProxy filterChainProxy = getFilterChainProxy();
|
||||||
|
|
||||||
assertTrue(filters.size() == 0);
|
List filterList = filterChainProxy.getFilters("/anyurl");
|
||||||
|
|
||||||
|
checkAutoConfigFilters(filterList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void checkAutoConfigFilters(List filterList) {
|
||||||
public void filterChainProxyShouldReturnCorrectFilterListForProtectedUrl() {
|
assertEquals("Expected 10 filters in chain", 10, filterList.size());
|
||||||
FilterChainProxy filterChainProxy =
|
|
||||||
(FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
|
||||||
|
|
||||||
List filterList = filterChainProxy.getFilters("/someurl");
|
|
||||||
|
|
||||||
assertEquals("Expected 12 filters in chain", 12, filterList.size());
|
|
||||||
|
|
||||||
Iterator filters = filterList.iterator();
|
Iterator filters = filterList.iterator();
|
||||||
|
|
||||||
assertTrue(filters.next() instanceof ChannelProcessingFilter);
|
|
||||||
assertTrue(filters.next() instanceof ConcurrentSessionFilter);
|
|
||||||
assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter);
|
assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter);
|
||||||
assertTrue(filters.next() instanceof LogoutFilter);
|
assertTrue(filters.next() instanceof LogoutFilter);
|
||||||
assertTrue(filters.next() instanceof AuthenticationProcessingFilter);
|
assertTrue(filters.next() instanceof AuthenticationProcessingFilter);
|
||||||
@ -78,17 +75,136 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
assertTrue(filters.next() instanceof BasicProcessingFilter);
|
assertTrue(filters.next() instanceof BasicProcessingFilter);
|
||||||
assertTrue(filters.next() instanceof SecurityContextHolderAwareRequestFilter);
|
assertTrue(filters.next() instanceof SecurityContextHolderAwareRequestFilter);
|
||||||
assertTrue(filters.next() instanceof RememberMeProcessingFilter);
|
assertTrue(filters.next() instanceof RememberMeProcessingFilter);
|
||||||
|
assertTrue(filters.next() instanceof AnonymousProcessingFilter);
|
||||||
assertTrue(filters.next() instanceof ExceptionTranslationFilter);
|
assertTrue(filters.next() instanceof ExceptionTranslationFilter);
|
||||||
assertTrue(filters.next() instanceof FilterSecurityInterceptor);
|
assertTrue(filters.next() instanceof FilterSecurityInterceptor);
|
||||||
assertTrue(filters.next() instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterListShouldBeEmptyForUnprotectedUrl() {
|
||||||
|
setContext(
|
||||||
|
" <http auto-config='true'>" +
|
||||||
|
" <intercept-url pattern='/unprotected' filters='none' />" +
|
||||||
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
|
||||||
|
FilterChainProxy filterChainProxy = getFilterChainProxy();
|
||||||
|
|
||||||
|
List filters = filterChainProxy.getFilters("/unprotected");
|
||||||
|
|
||||||
|
assertTrue(filters.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void regexPathsWorkCorrectly() {
|
||||||
|
setContext(
|
||||||
|
" <http auto-config='true' path-type='regex'>" +
|
||||||
|
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
|
||||||
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
FilterChainProxy filterChainProxy = getFilterChainProxy();
|
||||||
|
assertEquals(0, filterChainProxy.getFilters("/imlowercase").size());
|
||||||
|
// This will be matched by the default pattern ".*"
|
||||||
|
checkAutoConfigFilters(filterChainProxy.getFilters("/ImCaughtByTheUniversalMatchPattern"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void lowerCaseComparisonAttributeIsRespectedByFilterChainProxy() {
|
||||||
|
setContext(
|
||||||
|
" <http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
|
||||||
|
" <intercept-url pattern='/Secure*' filters='none' />" +
|
||||||
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
FilterChainProxy filterChainProxy = getFilterChainProxy();
|
||||||
|
assertEquals(0, filterChainProxy.getFilters("/Secure").size());
|
||||||
|
// These will be matched by the default pattern "/**"
|
||||||
|
checkAutoConfigFilters(filterChainProxy.getFilters("/secure"));
|
||||||
|
checkAutoConfigFilters(filterChainProxy.getFilters("/ImCaughtByTheUniversalMatchPattern"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void lowerCaseComparisonIsRespectedBySecurityFilterInvocationDefinitionSource() throws Exception {
|
||||||
|
setContext(
|
||||||
|
" <http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
|
||||||
|
" <intercept-url pattern='/Secure*' access='ROLE_A,ROLE_B' />" +
|
||||||
|
" <intercept-url pattern='/**' access='ROLE_C' />" +
|
||||||
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
|
||||||
|
FilterSecurityInterceptor fis = (FilterSecurityInterceptor) appContext.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR);
|
||||||
|
|
||||||
|
FilterInvocationDefinitionSource fids = fis.getObjectDefinitionSource();
|
||||||
|
ConfigAttributeDefinition attrs = fids.getAttributes(createFilterinvocation("/Secure"));
|
||||||
|
assertEquals(2, attrs.size());
|
||||||
|
assertTrue(attrs.contains(new SecurityConfig("ROLE_A")));
|
||||||
|
assertTrue(attrs.contains(new SecurityConfig("ROLE_B")));
|
||||||
|
attrs = fids.getAttributes(createFilterinvocation("/secure"));
|
||||||
|
assertEquals(1, attrs.size());
|
||||||
|
assertTrue(attrs.contains(new SecurityConfig("ROLE_C")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void minimalConfigurationParses() {
|
||||||
|
setContext("<http><http-basic /></http>" + AUTH_PROVIDER_XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void interceptUrlWithRequiresChannelAddsChannelFilterToStack() {
|
||||||
|
setContext(
|
||||||
|
" <http auto-config='true'>" +
|
||||||
|
" <intercept-url pattern='/**' requires-channel='https' />" +
|
||||||
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
FilterChainProxy filterChainProxy = getFilterChainProxy();
|
||||||
|
|
||||||
|
List filters = filterChainProxy.getFilters("/someurl");
|
||||||
|
|
||||||
|
assertEquals("Expected 11 filters in chain", 11, filters.size());
|
||||||
|
|
||||||
|
assertTrue(filters.get(0) instanceof ChannelProcessingFilter);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void portMappingsAreParsedCorrectly() throws Exception {
|
public void portMappingsAreParsedCorrectly() throws Exception {
|
||||||
|
setContext(
|
||||||
|
" <http auto-config='true'>" +
|
||||||
|
" <port-mappings>" +
|
||||||
|
" <port-mapping http='9080' https='9443'/>" +
|
||||||
|
" </port-mappings>" +
|
||||||
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
|
||||||
PortMapperImpl pm = (PortMapperImpl) appContext.getBean(BeanIds.PORT_MAPPER);
|
PortMapperImpl pm = (PortMapperImpl) appContext.getBean(BeanIds.PORT_MAPPER);
|
||||||
assertEquals(1, pm.getTranslatedPortMappings().size());
|
assertEquals(1, pm.getTranslatedPortMappings().size());
|
||||||
assertEquals(Integer.valueOf(9080), pm.lookupHttpPort(9443));
|
assertEquals(Integer.valueOf(9080), pm.lookupHttpPort(9443));
|
||||||
assertEquals(Integer.valueOf(9443), pm.lookupHttpsPort(9080));
|
assertEquals(Integer.valueOf(9443), pm.lookupHttpsPort(9080));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void externalFiltersAreTreatedCorrectly() {
|
||||||
|
// Decorated user-filter should be added to stack. The other one should be ignored
|
||||||
|
setContext(
|
||||||
|
"<http auto-config='true'/>" + AUTH_PROVIDER_XML +
|
||||||
|
"<b:bean id='userFilter' class='org.springframework.security.util.MockFilter'>" +
|
||||||
|
" <user-filter order='0'/>" +
|
||||||
|
"</b:bean>" +
|
||||||
|
"<b:bean id='userFilter2' class='org.springframework.security.util.MockFilter'/>");
|
||||||
|
List filters = getFilterChainProxy().getFilters("/someurl");
|
||||||
|
|
||||||
|
assertEquals(11, filters.size());
|
||||||
|
assertTrue(filters.get(10) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
|
||||||
|
assertEquals("userFilter", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(10)).getBeanName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setContext(String context) {
|
||||||
|
appContext = new InMemoryXmlApplicationContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterChainProxy getFilterChainProxy() {
|
||||||
|
return (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterInvocation createFilterinvocation(String path) {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setRequestURI(null);
|
||||||
|
|
||||||
|
request.setServletPath(path);
|
||||||
|
|
||||||
|
return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user