mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-04 09:42:29 +00:00
SEC-1407: Use RequestMatcher instances as the FilterInvocationSecurityMetadataSource keys and in the FilterChainMap use by FilterChainProxy.
This greatly simplifies the code and opens up possibilities for other matching strategies (e.g. EL). This also means that matching is now completely strict - the order of the matchers is all that matters (not whether an HTTP method is included or not). The first matcher that returns true will be used.
This commit is contained in:
parent
962a2d5272
commit
93438defff
@ -2,7 +2,6 @@ package org.springframework.security.config.http;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
|
|
||||||
@ -11,6 +10,7 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
import org.springframework.security.access.ConfigAttribute;
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
|
import org.springframework.security.web.FilterInvocation;
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
@ -22,18 +22,17 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
|
|||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
|
import org.springframework.security.web.util.AnyRequestMatcher;
|
||||||
|
|
||||||
public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator {
|
public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator {
|
||||||
private Log logger = LogFactory.getLog(getClass());
|
private Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
public void validate(FilterChainProxy fcp) {
|
public void validate(FilterChainProxy fcp) {
|
||||||
Map<String, List<Filter>> filterChainMap = fcp.getFilterChainMap();
|
for(List<Filter> filters : fcp.getFilterChainMap().values()) {
|
||||||
for(String pattern : fcp.getFilterChainMap().keySet()) {
|
|
||||||
List<Filter> filters = filterChainMap.get(pattern);
|
|
||||||
checkFilterStack(filters);
|
checkFilterStack(filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkLoginPageIsntProtected(fcp, filterChainMap.get(fcp.getMatcher().getUniversalMatchPattern()));
|
checkLoginPageIsntProtected(fcp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getFilter(Class<?> type, List<Filter> filters) {
|
private Object getFilter(Class<?> type, List<Filter> filters) {
|
||||||
@ -78,12 +77,14 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Checks for the common error of having a login page URL protected by the security interceptor */
|
/* Checks for the common error of having a login page URL protected by the security interceptor */
|
||||||
private void checkLoginPageIsntProtected(FilterChainProxy fcp, List<Filter> defaultFilters) {
|
private void checkLoginPageIsntProtected(FilterChainProxy fcp) {
|
||||||
|
List<Filter> defaultFilters = fcp.getFilterChainMap().get(new AnyRequestMatcher());
|
||||||
ExceptionTranslationFilter etf = (ExceptionTranslationFilter)getFilter(ExceptionTranslationFilter.class, defaultFilters);
|
ExceptionTranslationFilter etf = (ExceptionTranslationFilter)getFilter(ExceptionTranslationFilter.class, defaultFilters);
|
||||||
|
|
||||||
if (etf.getAuthenticationEntryPoint() instanceof LoginUrlAuthenticationEntryPoint) {
|
if (etf.getAuthenticationEntryPoint() instanceof LoginUrlAuthenticationEntryPoint) {
|
||||||
String loginPage =
|
String loginPage =
|
||||||
((LoginUrlAuthenticationEntryPoint)etf.getAuthenticationEntryPoint()).getLoginFormUrl();
|
((LoginUrlAuthenticationEntryPoint)etf.getAuthenticationEntryPoint()).getLoginFormUrl();
|
||||||
|
FilterInvocation loginRequest = new FilterInvocation(loginPage, "POST");
|
||||||
List<Filter> filters = fcp.getFilters(loginPage);
|
List<Filter> filters = fcp.getFilters(loginPage);
|
||||||
logger.info("Checking whether login URL '" + loginPage + "' is accessible with your configuration");
|
logger.info("Checking whether login URL '" + loginPage + "' is accessible with your configuration");
|
||||||
|
|
||||||
@ -100,7 +101,8 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain
|
|||||||
FilterSecurityInterceptor fsi = (FilterSecurityInterceptor) getFilter(FilterSecurityInterceptor.class, filters);
|
FilterSecurityInterceptor fsi = (FilterSecurityInterceptor) getFilter(FilterSecurityInterceptor.class, filters);
|
||||||
DefaultFilterInvocationSecurityMetadataSource fids =
|
DefaultFilterInvocationSecurityMetadataSource fids =
|
||||||
(DefaultFilterInvocationSecurityMetadataSource) fsi.getSecurityMetadataSource();
|
(DefaultFilterInvocationSecurityMetadataSource) fsi.getSecurityMetadataSource();
|
||||||
Collection<ConfigAttribute> attributes = fids.lookupAttributes(loginPage, "POST");
|
|
||||||
|
Collection<ConfigAttribute> attributes = fids.getAttributes(loginRequest);
|
||||||
|
|
||||||
if (attributes == null) {
|
if (attributes == null) {
|
||||||
logger.debug("No access attributes defined for login page URL");
|
logger.debug("No access attributes defined for login page URL");
|
||||||
@ -122,7 +124,7 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain
|
|||||||
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("key", anonPF.getUserAttribute().getPassword(),
|
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("key", anonPF.getUserAttribute().getPassword(),
|
||||||
anonPF.getUserAttribute().getAuthorities());
|
anonPF.getUserAttribute().getAuthorities());
|
||||||
try {
|
try {
|
||||||
fsi.getAccessDecisionManager().decide(token, new Object(), fids.lookupAttributes(loginPage, "POST"));
|
fsi.getAccessDecisionManager().decide(token, new Object(), attributes);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("Anonymous access to the login page doesn't appear to be enabled. This is almost certainly " +
|
logger.warn("Anonymous access to the login page doesn't appear to be enabled. This is almost certainly " +
|
||||||
"an error. Please check your configuration allows unauthenticated access to the configured " +
|
"an error. Please check your configuration allows unauthenticated access to the configured " +
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package org.springframework.security.config.http;
|
package org.springframework.security.config.http;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||||
@ -8,14 +13,11 @@ import org.springframework.beans.factory.support.ManagedMap;
|
|||||||
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
|
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
|
||||||
import org.springframework.beans.factory.xml.ParserContext;
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
import org.springframework.security.config.Elements;
|
import org.springframework.security.config.Elements;
|
||||||
import org.springframework.security.web.util.RegexUrlPathMatcher;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
import org.springframework.util.xml.DomUtils;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the filter chain Map for a FilterChainProxy bean declaration.
|
* Sets the filter chain Map for a FilterChainProxy bean declaration.
|
||||||
*
|
*
|
||||||
@ -30,11 +32,7 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
|
|||||||
Map filterChainMap = new LinkedHashMap();
|
Map filterChainMap = new LinkedHashMap();
|
||||||
Element elt = (Element)node;
|
Element elt = (Element)node;
|
||||||
|
|
||||||
String pathType = elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_PATH_TYPE);
|
MatcherType matcherType = MatcherType.fromElement(elt);
|
||||||
|
|
||||||
if (HttpSecurityBeanDefinitionParser.OPT_PATH_TYPE_REGEX.equals(pathType)) {
|
|
||||||
filterChainProxy.getPropertyValues().addPropertyValue("matcher", new RegexUrlPathMatcher());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Element> filterChainElts = DomUtils.getChildElementsByTagName(elt, Elements.FILTER_CHAIN);
|
List<Element> filterChainElts = DomUtils.getChildElementsByTagName(elt, Elements.FILTER_CHAIN);
|
||||||
|
|
||||||
@ -52,8 +50,10 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
|
|||||||
"'must not be empty", elt);
|
"'must not be empty", elt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BeanDefinition matcher = matcherType.createMatcher(path, null);
|
||||||
|
|
||||||
if (filters.equals(HttpSecurityBeanDefinitionParser.OPT_FILTERS_NONE)) {
|
if (filters.equals(HttpSecurityBeanDefinitionParser.OPT_FILTERS_NONE)) {
|
||||||
filterChainMap.put(path, Collections.EMPTY_LIST);
|
filterChainMap.put(matcher, Collections.EMPTY_LIST);
|
||||||
} else {
|
} else {
|
||||||
String[] filterBeanNames = StringUtils.tokenizeToStringArray(filters, ",");
|
String[] filterBeanNames = StringUtils.tokenizeToStringArray(filters, ",");
|
||||||
ManagedList filterChain = new ManagedList(filterBeanNames.length);
|
ManagedList filterChain = new ManagedList(filterBeanNames.length);
|
||||||
@ -62,7 +62,7 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
|
|||||||
filterChain.add(new RuntimeBeanReference(filterBeanNames[i]));
|
filterChain.add(new RuntimeBeanReference(filterBeanNames[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChainMap.put(path, filterChain);
|
filterChainMap.put(matcher, filterChain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,9 +17,6 @@ import org.springframework.security.web.access.expression.DefaultWebSecurityExpr
|
|||||||
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.RequestKey;
|
|
||||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
|
||||||
import org.springframework.security.web.util.UrlMatcher;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
import org.springframework.util.xml.DomUtils;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@ -63,11 +60,11 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||||||
}
|
}
|
||||||
|
|
||||||
static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {
|
static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {
|
||||||
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(elt);
|
MatcherType matcherType = MatcherType.fromElement(elt);
|
||||||
boolean useExpressions = isUseExpressions(elt);
|
boolean useExpressions = isUseExpressions(elt);
|
||||||
|
|
||||||
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
|
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
|
||||||
interceptUrls, useExpressions, pc);
|
matcherType, interceptUrls, useExpressions, pc);
|
||||||
BeanDefinitionBuilder fidsBuilder;
|
BeanDefinitionBuilder fidsBuilder;
|
||||||
|
|
||||||
if (useExpressions) {
|
if (useExpressions) {
|
||||||
@ -83,16 +80,14 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||||||
}
|
}
|
||||||
|
|
||||||
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
|
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
|
||||||
fidsBuilder.addConstructorArgValue(matcher);
|
|
||||||
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
|
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
|
||||||
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
|
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
|
||||||
} else {
|
} else {
|
||||||
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
|
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
|
||||||
fidsBuilder.addConstructorArgValue(matcher);
|
|
||||||
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
|
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
|
// fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
|
||||||
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));
|
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));
|
||||||
|
|
||||||
return fidsBuilder.getBeanDefinition();
|
return fidsBuilder.getBeanDefinition();
|
||||||
@ -102,8 +97,9 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||||||
return "true".equals(elt.getAttribute(ATT_USE_EXPRESSIONS));
|
return "true".equals(elt.getAttribute(ATT_USE_EXPRESSIONS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts,
|
private static ManagedMap<BeanDefinition, BeanDefinition>
|
||||||
boolean useExpressions, ParserContext parserContext) {
|
parseInterceptUrlsForFilterInvocationRequestMap(MatcherType matcherType,
|
||||||
|
List<Element> urlElts, boolean useExpressions, ParserContext parserContext) {
|
||||||
|
|
||||||
ManagedMap<BeanDefinition, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanDefinition, BeanDefinition>();
|
ManagedMap<BeanDefinition, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanDefinition, BeanDefinition>();
|
||||||
|
|
||||||
@ -124,10 +120,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||||||
method = null;
|
method = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinitionBuilder keyBldr = BeanDefinitionBuilder.rootBeanDefinition(RequestKey.class);
|
BeanDefinition matcher = matcherType.createMatcher(path, method);
|
||||||
keyBldr.addConstructorArgValue(path);
|
|
||||||
keyBldr.addConstructorArgValue(method);
|
|
||||||
|
|
||||||
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder.rootBeanDefinition(SecurityConfig.class);
|
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder.rootBeanDefinition(SecurityConfig.class);
|
||||||
attributeBuilder.addConstructorArgValue(access);
|
attributeBuilder.addConstructorArgValue(access);
|
||||||
|
|
||||||
@ -140,13 +133,11 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
|
|||||||
attributeBuilder.setFactoryMethod("createListFromCommaDelimitedString");
|
attributeBuilder.setFactoryMethod("createListFromCommaDelimitedString");
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinition key = keyBldr.getBeanDefinition();
|
if (filterInvocationDefinitionMap.containsKey(matcher)) {
|
||||||
|
|
||||||
if (filterInvocationDefinitionMap.containsKey(key)) {
|
|
||||||
logger.warn("Duplicate URL defined: " + path + ". The original attribute values will be overwritten");
|
logger.warn("Duplicate URL defined: " + path + ". The original attribute values will be overwritten");
|
||||||
}
|
}
|
||||||
|
|
||||||
filterInvocationDefinitionMap.put(key, attributeBuilder.getBeanDefinition());
|
filterInvocationDefinitionMap.put(matcher, attributeBuilder.getBeanDefinition());
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterInvocationDefinitionMap;
|
return filterInvocationDefinitionMap;
|
||||||
|
@ -35,7 +35,6 @@ import org.springframework.security.web.access.channel.SecureChannelProcessor;
|
|||||||
import org.springframework.security.web.access.expression.WebExpressionVoter;
|
import org.springframework.security.web.access.expression.WebExpressionVoter;
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.access.intercept.RequestKey;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy;
|
import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy;
|
||||||
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
|
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
|
||||||
@ -48,8 +47,6 @@ import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
|||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||||
import org.springframework.security.web.session.ConcurrentSessionFilter;
|
import org.springframework.security.web.session.ConcurrentSessionFilter;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
|
||||||
import org.springframework.security.web.util.UrlMatcher;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
import org.springframework.util.xml.DomUtils;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@ -81,10 +78,9 @@ class HttpConfigurationBuilder {
|
|||||||
|
|
||||||
private final Element httpElt;
|
private final Element httpElt;
|
||||||
private final ParserContext pc;
|
private final ParserContext pc;
|
||||||
private final UrlMatcher matcher;
|
|
||||||
private final Boolean convertPathsToLowerCase;
|
|
||||||
private final SessionCreationPolicy sessionPolicy;
|
private final SessionCreationPolicy sessionPolicy;
|
||||||
private final List<Element> interceptUrls;
|
private final List<Element> interceptUrls;
|
||||||
|
private final MatcherType matcherType;
|
||||||
|
|
||||||
// Use ManagedMap to allow placeholder resolution
|
// Use ManagedMap to allow placeholder resolution
|
||||||
private ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap;
|
private ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap;
|
||||||
@ -102,15 +98,12 @@ class HttpConfigurationBuilder {
|
|||||||
private BeanReference fsi;
|
private BeanReference fsi;
|
||||||
private BeanReference requestCache;
|
private BeanReference requestCache;
|
||||||
|
|
||||||
public HttpConfigurationBuilder(Element element, ParserContext pc, UrlMatcher matcher,
|
public HttpConfigurationBuilder(Element element, ParserContext pc, MatcherType matcherType,
|
||||||
String portMapperName, BeanReference authenticationManager) {
|
String portMapperName, BeanReference authenticationManager) {
|
||||||
this.httpElt = element;
|
this.httpElt = element;
|
||||||
this.pc = pc;
|
this.pc = pc;
|
||||||
this.portMapperName = portMapperName;
|
this.portMapperName = portMapperName;
|
||||||
this.matcher = matcher;
|
this.matcherType = matcherType;
|
||||||
// SEC-501 - should paths stored in request maps be converted to lower case
|
|
||||||
// true if Ant path and using lower case
|
|
||||||
convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
|
|
||||||
interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
|
interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
|
||||||
String createSession = element.getAttribute(ATT_CREATE_SESSION);
|
String createSession = element.getAttribute(ATT_CREATE_SESSION);
|
||||||
|
|
||||||
@ -139,10 +132,7 @@ class HttpConfigurationBuilder {
|
|||||||
pc.getReaderContext().error("path attribute cannot be empty or null", urlElt);
|
pc.getReaderContext().error("path attribute cannot be empty or null", urlElt);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinitionBuilder pathBean = BeanDefinitionBuilder.rootBeanDefinition(HttpConfigurationBuilder.class);
|
BeanDefinition matcher = matcherType.createMatcher(path, null);
|
||||||
pathBean.setFactoryMethod("createPath");
|
|
||||||
pathBean.addConstructorArgValue(path);
|
|
||||||
pathBean.addConstructorArgValue(convertPathsToLowerCase);
|
|
||||||
|
|
||||||
String filters = urlElt.getAttribute(ATT_FILTERS);
|
String filters = urlElt.getAttribute(ATT_FILTERS);
|
||||||
|
|
||||||
@ -153,7 +143,7 @@ class HttpConfigurationBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<BeanMetadataElement> noFilters = Collections.emptyList();
|
List<BeanMetadataElement> noFilters = Collections.emptyList();
|
||||||
filterChainMap.put(pathBean.getBeanDefinition(), noFilters);
|
filterChainMap.put(matcher, noFilters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,9 +368,8 @@ class HttpConfigurationBuilder {
|
|||||||
|
|
||||||
RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class);
|
RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class);
|
||||||
BeanDefinitionBuilder metadataSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
|
BeanDefinitionBuilder metadataSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
|
||||||
metadataSourceBldr.addConstructorArgValue(matcher);
|
|
||||||
metadataSourceBldr.addConstructorArgValue(channelRequestMap);
|
metadataSourceBldr.addConstructorArgValue(channelRequestMap);
|
||||||
metadataSourceBldr.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
|
// metadataSourceBldr.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
|
||||||
|
|
||||||
channelFilter.getPropertyValues().addPropertyValue("securityMetadataSource", metadataSourceBldr.getBeanDefinition());
|
channelFilter.getPropertyValues().addPropertyValue("securityMetadataSource", metadataSourceBldr.getBeanDefinition());
|
||||||
RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class);
|
RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class);
|
||||||
@ -413,26 +402,22 @@ class HttpConfigurationBuilder {
|
|||||||
|
|
||||||
for (Element urlElt : interceptUrls) {
|
for (Element urlElt : interceptUrls) {
|
||||||
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
|
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
|
||||||
|
String method = urlElt.getAttribute(ATT_HTTP_METHOD);
|
||||||
|
|
||||||
if(!StringUtils.hasText(path)) {
|
if(!StringUtils.hasText(path)) {
|
||||||
pc.getReaderContext().error("path attribute cannot be empty or null", urlElt);
|
pc.getReaderContext().error("pattern attribute cannot be empty or null", urlElt);
|
||||||
}
|
|
||||||
|
|
||||||
if (convertPathsToLowerCase) {
|
|
||||||
path = path.toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
|
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
|
||||||
|
|
||||||
if (StringUtils.hasText(requiredChannel)) {
|
if (StringUtils.hasText(requiredChannel)) {
|
||||||
BeanDefinition requestKey = new RootBeanDefinition(RequestKey.class);
|
BeanDefinition matcher = matcherType.createMatcher(path, method);
|
||||||
requestKey.getConstructorArgumentValues().addGenericArgumentValue(path);
|
|
||||||
|
|
||||||
RootBeanDefinition channelAttributes = new RootBeanDefinition(ChannelAttributeFactory.class);
|
RootBeanDefinition channelAttributes = new RootBeanDefinition(ChannelAttributeFactory.class);
|
||||||
channelAttributes.getConstructorArgumentValues().addGenericArgumentValue(requiredChannel);
|
channelAttributes.getConstructorArgumentValues().addGenericArgumentValue(requiredChannel);
|
||||||
channelAttributes.setFactoryMethodName("createChannelAttributes");
|
channelAttributes.setFactoryMethodName("createChannelAttributes");
|
||||||
|
|
||||||
channelRequestMap.put(requestKey, channelAttributes);
|
channelRequestMap.put(matcher, channelAttributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +26,7 @@ import org.springframework.security.config.BeanIds;
|
|||||||
import org.springframework.security.config.Elements;
|
import org.springframework.security.config.Elements;
|
||||||
import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
|
import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
import org.springframework.security.web.util.AnyRequestMatcher;
|
||||||
import org.springframework.security.web.util.RegexUrlPathMatcher;
|
|
||||||
import org.springframework.security.web.util.UrlMatcher;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
import org.springframework.util.xml.DomUtils;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@ -44,17 +42,13 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
private static final Log logger = LogFactory.getLog(HttpSecurityBeanDefinitionParser.class);
|
private static final Log logger = LogFactory.getLog(HttpSecurityBeanDefinitionParser.class);
|
||||||
|
|
||||||
static final String ATT_PATH_PATTERN = "pattern";
|
static final String ATT_PATH_PATTERN = "pattern";
|
||||||
static final String ATT_PATH_TYPE = "path-type";
|
static final String ATT_HTTP_METHOD = "method";
|
||||||
static final String OPT_PATH_TYPE_REGEX = "regex";
|
|
||||||
private static final String DEF_PATH_TYPE_ANT = "ant";
|
|
||||||
|
|
||||||
static final String ATT_FILTERS = "filters";
|
static final String ATT_FILTERS = "filters";
|
||||||
static final String OPT_FILTERS_NONE = "none";
|
static final String OPT_FILTERS_NONE = "none";
|
||||||
|
|
||||||
static final String ATT_REQUIRES_CHANNEL = "requires-channel";
|
static final String ATT_REQUIRES_CHANNEL = "requires-channel";
|
||||||
|
|
||||||
private static final String ATT_LOWERCASE_COMPARISONS = "lowercase-comparisons";
|
|
||||||
|
|
||||||
private static final String ATT_REF = "ref";
|
private static final String ATT_REF = "ref";
|
||||||
|
|
||||||
static final String EXPRESSION_FIMDS_CLASS = "org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource";
|
static final String EXPRESSION_FIMDS_CLASS = "org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource";
|
||||||
@ -80,25 +74,25 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
final Object source = pc.extractSource(element);
|
final Object source = pc.extractSource(element);
|
||||||
|
|
||||||
final String portMapperName = createPortMapper(element, pc);
|
final String portMapperName = createPortMapper(element, pc);
|
||||||
final UrlMatcher matcher = createUrlMatcher(element);
|
|
||||||
|
MatcherType matcherType = MatcherType.fromElement(element);
|
||||||
|
|
||||||
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
|
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
|
||||||
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);
|
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);
|
||||||
|
|
||||||
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcher,
|
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcherType,
|
||||||
portMapperName, authenticationManager);
|
portMapperName, authenticationManager);
|
||||||
|
|
||||||
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
|
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
|
||||||
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
|
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
|
||||||
httpBldr.getSessionStrategy());
|
httpBldr.getSessionStrategy());
|
||||||
|
|
||||||
|
authenticationProviders.addAll(authBldr.getProviders());
|
||||||
|
|
||||||
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
|
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
|
||||||
|
|
||||||
unorderedFilterChain.addAll(httpBldr.getFilters());
|
unorderedFilterChain.addAll(httpBldr.getFilters());
|
||||||
unorderedFilterChain.addAll(authBldr.getFilters());
|
unorderedFilterChain.addAll(authBldr.getFilters());
|
||||||
|
|
||||||
authenticationProviders.addAll(authBldr.getProviders());
|
|
||||||
|
|
||||||
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
|
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
|
||||||
|
|
||||||
Collections.sort(unorderedFilterChain, new OrderComparator());
|
Collections.sort(unorderedFilterChain, new OrderComparator());
|
||||||
@ -111,11 +105,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap = httpBldr.getFilterChainMap();
|
ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap = httpBldr.getFilterChainMap();
|
||||||
BeanDefinition universalMatch = new RootBeanDefinition(String.class);
|
BeanDefinition universalMatch = new RootBeanDefinition(AnyRequestMatcher.class);
|
||||||
universalMatch.getConstructorArgumentValues().addGenericArgumentValue(matcher.getUniversalMatchPattern());
|
|
||||||
filterChainMap.put(universalMatch, filterChain);
|
filterChainMap.put(universalMatch, filterChain);
|
||||||
|
|
||||||
registerFilterChainProxy(pc, filterChainMap, matcher, source);
|
registerFilterChainProxy(pc, filterChainMap, source);
|
||||||
|
|
||||||
pc.popAndRegisterContainingComponent();
|
pc.popAndRegisterContainingComponent();
|
||||||
return null;
|
return null;
|
||||||
@ -222,57 +215,20 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
return customFilters;
|
return customFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerFilterChainProxy(ParserContext pc, Map<BeanDefinition, List<BeanMetadataElement>> filterChainMap, UrlMatcher matcher, Object source) {
|
private void registerFilterChainProxy(ParserContext pc, Map<BeanDefinition, List<BeanMetadataElement>> filterChainMap, Object source) {
|
||||||
if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
|
if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
|
||||||
pc.getReaderContext().error("Duplicate <http> element detected", source);
|
pc.getReaderContext().error("Duplicate <http> element detected", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
|
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
|
||||||
fcpBldr.getRawBeanDefinition().setSource(source);
|
fcpBldr.getRawBeanDefinition().setSource(source);
|
||||||
fcpBldr.addPropertyValue("matcher", matcher);
|
// fcpBldr.addPropertyValue("stripQueryStringFromUrls", Boolean.valueOf(matcher instanceof AntUrlPathMatcher));
|
||||||
fcpBldr.addPropertyValue("stripQueryStringFromUrls", Boolean.valueOf(matcher instanceof AntUrlPathMatcher));
|
|
||||||
fcpBldr.addPropertyValue("filterChainMap", filterChainMap);
|
fcpBldr.addPropertyValue("filterChainMap", filterChainMap);
|
||||||
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
|
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
|
||||||
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
|
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
|
||||||
pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
static UrlMatcher createUrlMatcher(Element element) {
|
|
||||||
String patternType = element.getAttribute(ATT_PATH_TYPE);
|
|
||||||
if (!StringUtils.hasText(patternType)) {
|
|
||||||
patternType = DEF_PATH_TYPE_ANT;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean useRegex = patternType.equals(OPT_PATH_TYPE_REGEX);
|
|
||||||
|
|
||||||
UrlMatcher matcher = new AntUrlPathMatcher();
|
|
||||||
|
|
||||||
if (useRegex) {
|
|
||||||
matcher = new RegexUrlPathMatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with lowercase conversion requests
|
|
||||||
String lowercaseComparisons = element.getAttribute(ATT_LOWERCASE_COMPARISONS);
|
|
||||||
if (!StringUtils.hasText(lowercaseComparisons)) {
|
|
||||||
lowercaseComparisons = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only change from the defaults if the attribute has been set
|
|
||||||
if ("true".equals(lowercaseComparisons)) {
|
|
||||||
if (useRegex) {
|
|
||||||
((RegexUrlPathMatcher)matcher).setRequiresLowerCaseUrl(true);
|
|
||||||
}
|
|
||||||
// Default for ant is already to force lower case
|
|
||||||
} else if ("false".equals(lowercaseComparisons)) {
|
|
||||||
if (!useRegex) {
|
|
||||||
((AntUrlPathMatcher)matcher).setRequiresLowerCaseUrl(false);
|
|
||||||
}
|
|
||||||
// Default for regex is no change
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class OrderDecorator implements Ordered {
|
class OrderDecorator implements Ordered {
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package org.springframework.security.config.http;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.security.web.util.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.AnyRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.RegexRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the {@link RequestMatcher} types supported by the namespace.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public enum MatcherType {
|
||||||
|
ant (AntPathRequestMatcher.class),
|
||||||
|
regex (RegexRequestMatcher.class),
|
||||||
|
ciRegex (RegexRequestMatcher.class);
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(HttpSecurityBeanDefinitionParser.class);
|
||||||
|
|
||||||
|
private static final String ATT_MATCHER_TYPE = "request-matcher";
|
||||||
|
private static final String ATT_PATH_TYPE = "path-type";
|
||||||
|
|
||||||
|
private final Class<? extends RequestMatcher> type;
|
||||||
|
|
||||||
|
MatcherType(Class<? extends RequestMatcher> type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanDefinition createMatcher(String path, String method) {
|
||||||
|
if ("/**".equals(path)) {
|
||||||
|
return new RootBeanDefinition(AnyRequestMatcher.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanDefinitionBuilder matcherBldr = BeanDefinitionBuilder.rootBeanDefinition(type);
|
||||||
|
|
||||||
|
matcherBldr.addConstructorArgValue(path);
|
||||||
|
matcherBldr.addConstructorArgValue(method);
|
||||||
|
|
||||||
|
if (this == ciRegex) {
|
||||||
|
matcherBldr.addConstructorArgValue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcherBldr.getBeanDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
static MatcherType fromElement(Element elt) {
|
||||||
|
if (StringUtils.hasText(elt.getAttribute(ATT_MATCHER_TYPE))) {
|
||||||
|
return valueOf(elt.getAttribute(ATT_MATCHER_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasText(elt.getAttribute(ATT_PATH_TYPE))) {
|
||||||
|
logger.warn("'" + ATT_PATH_TYPE + "' is deprecated. Please use '" + ATT_MATCHER_TYPE +"' instead.");
|
||||||
|
return valueOf(elt.getAttribute(ATT_PATH_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ant;
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,9 @@ hash =
|
|||||||
base64 =
|
base64 =
|
||||||
## Whether a string should be base64 encoded
|
## Whether a string should be base64 encoded
|
||||||
attribute base64 {"true" | "false"}
|
attribute base64 {"true" | "false"}
|
||||||
|
request-matcher =
|
||||||
|
## Supersedes the 'path-type' attribute. Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'iciRegex' for case-insensitive regular expressions.
|
||||||
|
attribute request-matcher {"ant" | "regex" | "ciRegex"}
|
||||||
path-type =
|
path-type =
|
||||||
## Defines the type of pattern used to specify URL paths (either JDK 1.4-compatible regular expressions, or Apache Ant expressions). Defaults to "ant" if unspecified.
|
## Defines the type of pattern used to specify URL paths (either JDK 1.4-compatible regular expressions, or Apache Ant expressions). Defaults to "ant" if unspecified.
|
||||||
attribute path-type {"ant" | "regex"}
|
attribute path-type {"ant" | "regex"}
|
||||||
@ -264,11 +267,10 @@ http.attlist &=
|
|||||||
## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.
|
## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.
|
||||||
attribute security-context-repository-ref {xsd:token}?
|
attribute security-context-repository-ref {xsd:token}?
|
||||||
http.attlist &=
|
http.attlist &=
|
||||||
## The path format used to define the paths in child elements.
|
request-matcher?
|
||||||
path-type?
|
|
||||||
http.attlist &=
|
http.attlist &=
|
||||||
## Whether test URLs should be converted to lower case prior to comparing with defined path patterns. If unspecified, defaults to "true".
|
## Deprecated. Use request-matcher instead.
|
||||||
attribute lowercase-comparisons {boolean}?
|
path-type?
|
||||||
http.attlist &=
|
http.attlist &=
|
||||||
## Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".
|
## Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".
|
||||||
attribute servlet-api-provision {boolean}?
|
attribute servlet-api-provision {boolean}?
|
||||||
@ -392,7 +394,10 @@ filter-chain-map =
|
|||||||
## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
|
## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
|
||||||
element filter-chain-map {filter-chain-map.attlist, filter-chain+}
|
element filter-chain-map {filter-chain-map.attlist, filter-chain+}
|
||||||
filter-chain-map.attlist &=
|
filter-chain-map.attlist &=
|
||||||
path-type
|
## Deprecated. Use request-matcher instead.
|
||||||
|
path-type?
|
||||||
|
filter-chain-map.attlist &=
|
||||||
|
request-matcher?
|
||||||
|
|
||||||
filter-chain =
|
filter-chain =
|
||||||
## Used within filter-chain-map to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are used within a filter-chain-map element, the most specific patterns must be placed at the top of the list, with most general ones at the bottom.
|
## Used within filter-chain-map to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are used within a filter-chain-map element, the most specific patterns must be placed at the top of the list, with most general ones at the bottom.
|
||||||
@ -413,8 +418,10 @@ fsmds.attlist &=
|
|||||||
## as for http element
|
## as for http element
|
||||||
attribute lowercase-comparisons {boolean}?
|
attribute lowercase-comparisons {boolean}?
|
||||||
fsmds.attlist &=
|
fsmds.attlist &=
|
||||||
## as for http element
|
## Deprecate. Use request-matcher instead.
|
||||||
path-type?
|
path-type?
|
||||||
|
fsmds.attlist &=
|
||||||
|
request-matcher?
|
||||||
|
|
||||||
filter-invocation-definition-source =
|
filter-invocation-definition-source =
|
||||||
## Deprecated synonym for filter-security-metadata-source
|
## Deprecated synonym for filter-security-metadata-source
|
||||||
|
@ -31,6 +31,20 @@
|
|||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
|
<xs:attributeGroup name="request-matcher">
|
||||||
|
<xs:attribute name="request-matcher" use="required">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Supersedes the 'path-type' attribute. Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'iciRegex' for case-insensitive regular expressions.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="ant"/>
|
||||||
|
<xs:enumeration value="regex"/>
|
||||||
|
<xs:enumeration value="ciRegex"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:attributeGroup>
|
||||||
<xs:attributeGroup name="path-type">
|
<xs:attributeGroup name="path-type">
|
||||||
<xs:attribute name="path-type" use="required">
|
<xs:attribute name="path-type" use="required">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
@ -692,6 +706,18 @@
|
|||||||
<xs:documentation>A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.</xs:documentation>
|
<xs:documentation>A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="request-matcher">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Superseded the 'path-type' attribute. Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'iciRegex' for case-insensitive regular expressions.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="ant"/>
|
||||||
|
<xs:enumeration value="regex"/>
|
||||||
|
<xs:enumeration value="ciRegex"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
<xs:attribute name="path-type">
|
<xs:attribute name="path-type">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Defines the type of pattern used to specify URL paths (either JDK 1.4-compatible regular expressions, or Apache Ant expressions). Defaults to "ant" if unspecified.</xs:documentation>
|
<xs:documentation>Defines the type of pattern used to specify URL paths (either JDK 1.4-compatible regular expressions, or Apache Ant expressions). Defaults to "ant" if unspecified.</xs:documentation>
|
||||||
@ -703,11 +729,6 @@
|
|||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="lowercase-comparisons" type="security:boolean">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>Whether test URLs should be converted to lower case prior to comparing with defined path patterns. If unspecified, defaults to "true".</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
<xs:attribute name="servlet-api-provision" type="security:boolean">
|
<xs:attribute name="servlet-api-provision" type="security:boolean">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".</xs:documentation>
|
<xs:documentation>Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".</xs:documentation>
|
||||||
@ -902,7 +923,29 @@
|
|||||||
<xs:attributeGroup ref="security:filter-chain-map.attlist"/>
|
<xs:attributeGroup ref="security:filter-chain-map.attlist"/>
|
||||||
</xs:complexType></xs:element>
|
</xs:complexType></xs:element>
|
||||||
<xs:attributeGroup name="filter-chain-map.attlist">
|
<xs:attributeGroup name="filter-chain-map.attlist">
|
||||||
<xs:attributeGroup ref="security:path-type"/>
|
<xs:attribute name="path-type">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Defines the type of pattern used to specify URL paths (either JDK 1.4-compatible regular expressions, or Apache Ant expressions). Defaults to "ant" if unspecified.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="ant"/>
|
||||||
|
<xs:enumeration value="regex"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="request-matcher">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Superseded the 'path-type' attribute. Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'iciRegex' for case-insensitive regular expressions.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="ant"/>
|
||||||
|
<xs:enumeration value="regex"/>
|
||||||
|
<xs:enumeration value="ciRegex"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
|
|
||||||
<xs:attributeGroup name="filter-chain.attlist">
|
<xs:attributeGroup name="filter-chain.attlist">
|
||||||
@ -948,6 +991,18 @@
|
|||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="request-matcher">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Superseded the 'path-type' attribute. Defines the strategy use for matching incoming requests. Currently the options are 'ant' (for ant path patterns), 'regex' for regular expressions and 'iciRegex' for case-insensitive regular expressions.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="ant"/>
|
||||||
|
<xs:enumeration value="regex"/>
|
||||||
|
<xs:enumeration value="ciRegex"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
<xs:element name="filter-invocation-definition-source"><xs:annotation>
|
<xs:element name="filter-invocation-definition-source"><xs:annotation>
|
||||||
<xs:documentation>Deprecated synonym for filter-security-metadata-source</xs:documentation>
|
<xs:documentation>Deprecated synonym for filter-security-metadata-source</xs:documentation>
|
||||||
|
@ -37,6 +37,9 @@ import org.springframework.security.web.FilterChainProxy;
|
|||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||||
|
import org.springframework.security.web.util.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.AnyRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FilterChainProxy}.
|
* Tests {@link FilterChainProxy}.
|
||||||
@ -101,27 +104,15 @@ public class FilterChainProxyConfigTests {
|
|||||||
assertEquals(null, filterChainProxy.getFilters("/nomatch"));
|
assertEquals(null, filterChainProxy.getFilters("/nomatch"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void urlStrippingPropertyIsRespected() throws Exception {
|
|
||||||
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNoDefaultPath", FilterChainProxy.class);
|
|
||||||
|
|
||||||
// Should only match if we are stripping the query string
|
|
||||||
String url = "/blah.bar?x=something";
|
|
||||||
assertNotNull(filterChainProxy.getFilters(url));
|
|
||||||
assertEquals(2, filterChainProxy.getFilters(url).size());
|
|
||||||
filterChainProxy.setStripQueryStringFromUrls(false);
|
|
||||||
assertNull(filterChainProxy.getFilters(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
// SEC-1235
|
// SEC-1235
|
||||||
@Test
|
@Test
|
||||||
public void mixingPatternsAndPlaceholdersDoesntCauseOrderingIssues() throws Exception {
|
public void mixingPatternsAndPlaceholdersDoesntCauseOrderingIssues() throws Exception {
|
||||||
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class);
|
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class);
|
||||||
|
|
||||||
String[] paths = filterChainProxy.getFilterChainMap().keySet().toArray(new String[0]);
|
RequestMatcher[] matchers = filterChainProxy.getFilterChainMap().keySet().toArray(new RequestMatcher[0]);
|
||||||
assertEquals("/login*", paths[0]);
|
assertEquals("/login*", ((AntPathRequestMatcher)matchers[0]).getPattern());
|
||||||
assertEquals("/logout", paths[1]);
|
assertEquals("/logout", ((AntPathRequestMatcher)matchers[1]).getPattern());
|
||||||
assertEquals("/**", paths[2]);
|
assertTrue(matchers[2] instanceof AnyRequestMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {
|
private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {
|
||||||
|
@ -125,9 +125,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
List<Filter> filterList = getFilters("/anyurl");
|
List<Filter> filterList = getFilters("/anyurl");
|
||||||
|
|
||||||
checkAutoConfigFilters(filterList);
|
checkAutoConfigFilters(filterList);
|
||||||
|
|
||||||
assertEquals(true, FieldUtils.getFieldValue(appContext.getBean(BeanIds.FILTER_CHAIN_PROXY), "stripQueryStringFromUrls"));
|
|
||||||
assertEquals(true, FieldUtils.getFieldValue(filterList.get(AUTO_CONFIG_FILTERS-1), "securityMetadataSource.stripQueryStringFromUrls"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
@ -136,8 +133,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkAutoConfigFilters(List<Filter> filterList) throws Exception {
|
private void checkAutoConfigFilters(List<Filter> filterList) throws Exception {
|
||||||
// assertEquals("Expected " + AUTO_CONFIG_FILTERS + " filters in chain", AUTO_CONFIG_FILTERS, filterList.size());
|
|
||||||
|
|
||||||
Iterator<Filter> filters = filterList.iterator();
|
Iterator<Filter> filters = filterList.iterator();
|
||||||
|
|
||||||
assertTrue(filters.next() instanceof SecurityContextPersistenceFilter);
|
assertTrue(filters.next() instanceof SecurityContextPersistenceFilter);
|
||||||
@ -184,37 +179,34 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
assertTrue(filters.size() == 0);
|
assertTrue(filters.size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void regexPathsWorkCorrectly() throws Exception {
|
public void regexPathsWorkCorrectly() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
" <http auto-config='true' path-type='regex'>" +
|
" <http auto-config='true' request-matcher='regex'>" +
|
||||||
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
|
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
|
||||||
" </http>" + AUTH_PROVIDER_XML);
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
assertEquals(0, getFilters("/imlowercase").size());
|
assertEquals(0, getFilters("/imlowercase").size());
|
||||||
// This will be matched by the default pattern ".*"
|
List<Filter> allFilters = getFilters("/ImCaughtByTheAnyRequestMatcher");
|
||||||
List<Filter> allFilters = getFilters("/ImCaughtByTheUniversalMatchPattern");
|
|
||||||
checkAutoConfigFilters(allFilters);
|
checkAutoConfigFilters(allFilters);
|
||||||
assertEquals(false, FieldUtils.getFieldValue(appContext.getBean(BeanIds.FILTER_CHAIN_PROXY), "stripQueryStringFromUrls"));
|
|
||||||
assertEquals(false, FieldUtils.getFieldValue(allFilters.get(AUTO_CONFIG_FILTERS-1), "securityMetadataSource.stripQueryStringFromUrls"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lowerCaseComparisonAttributeIsRespectedByFilterChainProxy() throws Exception {
|
public void ciRegexPathsWorkCorrectly() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
" <http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
|
" <http auto-config='true' request-matcher='ciRegex'>" +
|
||||||
" <intercept-url pattern='/Secure*' filters='none' />" +
|
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
|
||||||
" </http>" + AUTH_PROVIDER_XML);
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
assertEquals(0, getFilters("/Secure").size());
|
assertEquals(0, getFilters("/imMixedCase").size());
|
||||||
// These will be matched by the default pattern "/**"
|
// This will be matched by the default pattern ".*"
|
||||||
checkAutoConfigFilters(getFilters("/secure"));
|
List<Filter> allFilters = getFilters("/Im_Caught_By_The_AnyRequestMatcher");
|
||||||
checkAutoConfigFilters(getFilters("/ImCaughtByTheUniversalMatchPattern"));
|
assertTrue(allFilters.size() > 0);
|
||||||
|
checkAutoConfigFilters(allFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void formLoginWithNoLoginPageAddsDefaultLoginPageFilter() throws Exception {
|
public void formLoginWithNoLoginPageAddsDefaultLoginPageFilter() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
"<http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
|
"<http auto-config='true' request-matcher='ant'>" +
|
||||||
" <form-login />" +
|
" <form-login />" +
|
||||||
"</http>" + AUTH_PROVIDER_XML);
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
// These will be matched by the default pattern "/**"
|
// These will be matched by the default pattern "/**"
|
||||||
@ -315,26 +307,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
assertSame(appContext.getBean("logoutHandler"), handler);
|
assertSame(appContext.getBean("logoutHandler"), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 = getFilter(FilterSecurityInterceptor.class);
|
|
||||||
|
|
||||||
FilterInvocationSecurityMetadataSource fids = fis.getSecurityMetadataSource();
|
|
||||||
Collection<ConfigAttribute> attrDef = fids.getAttributes(createFilterinvocation("/Secure", null));
|
|
||||||
assertEquals(2, attrDef.size());
|
|
||||||
assertTrue(attrDef.contains(new SecurityConfig("ROLE_A")));
|
|
||||||
assertTrue(attrDef.contains(new SecurityConfig("ROLE_B")));
|
|
||||||
attrDef = fids.getAttributes(createFilterinvocation("/secure", null));
|
|
||||||
assertEquals(1, attrDef.size());
|
|
||||||
assertTrue(attrDef.contains(new SecurityConfig("ROLE_C")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// SEC-1201
|
// SEC-1201
|
||||||
@Test
|
@Test
|
||||||
public void interceptUrlsAndFormLoginSupportPropertyPlaceholders() throws Exception {
|
public void interceptUrlsAndFormLoginSupportPropertyPlaceholders() throws Exception {
|
||||||
@ -395,9 +367,9 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
public void httpMethodMatchIsSupported() throws Exception {
|
public void httpMethodMatchIsSupported() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
" <http auto-config='true'>" +
|
" <http auto-config='true'>" +
|
||||||
" <intercept-url pattern='/**' access='ROLE_C' />" +
|
|
||||||
" <intercept-url pattern='/secure*' method='DELETE' access='ROLE_SUPERVISOR' />" +
|
" <intercept-url pattern='/secure*' method='DELETE' access='ROLE_SUPERVISOR' />" +
|
||||||
" <intercept-url pattern='/secure*' method='POST' access='ROLE_A,ROLE_B' />" +
|
" <intercept-url pattern='/secure*' method='POST' access='ROLE_A,ROLE_B' />" +
|
||||||
|
" <intercept-url pattern='/**' access='ROLE_C' />" +
|
||||||
" </http>" + AUTH_PROVIDER_XML);
|
" </http>" + AUTH_PROVIDER_XML);
|
||||||
|
|
||||||
FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);
|
FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);
|
||||||
@ -692,7 +664,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
"</http>" +
|
"</http>" +
|
||||||
"<b:bean id='userService' class='org.springframework.security.core.userdetails.MockUserDetailsService'/> " +
|
"<b:bean id='userService' class='org.springframework.security.core.userdetails.MockUserDetailsService'/> " +
|
||||||
AUTH_PROVIDER_XML);
|
AUTH_PROVIDER_XML);
|
||||||
// AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -746,7 +717,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
"</http>" +
|
"</http>" +
|
||||||
"<b:bean id='ss' class='org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy'/>"
|
"<b:bean id='ss' class='org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy'/>"
|
||||||
+ AUTH_PROVIDER_XML);
|
+ AUTH_PROVIDER_XML);
|
||||||
//session-authentication-strategy-ref
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -768,12 +738,10 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
|
getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
|
||||||
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
|
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
|
||||||
getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
|
getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
|
||||||
// Object sessionRegistryFromController = FieldUtils.getFieldValue(getConcurrentSessionController(),"sessionRegistry");
|
|
||||||
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
|
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
|
||||||
getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry");
|
getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry");
|
||||||
|
|
||||||
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
|
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
|
||||||
// assertSame(sessionRegistry, sessionRegistryFromController);
|
|
||||||
assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
|
assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
|
||||||
// SEC-1143
|
// SEC-1143
|
||||||
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
|
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
|
||||||
@ -791,8 +759,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
|
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
|
||||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||||
// Register 2 sessions and then check a third
|
// Register 2 sessions and then check a third
|
||||||
// req.setSession(new MockHttpSession());
|
|
||||||
// auth.setDetails(new WebAuthenticationDetails(req));
|
|
||||||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||||
SaveContextOnUpdateOrErrorResponseWrapper response = new SaveContextOnUpdateOrErrorResponseWrapper(mockResponse, false) {
|
SaveContextOnUpdateOrErrorResponseWrapper response = new SaveContextOnUpdateOrErrorResponseWrapper(mockResponse, false) {
|
||||||
protected void saveContext(SecurityContext context) {
|
protected void saveContext(SecurityContext context) {
|
||||||
@ -1240,7 +1206,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setMethod(method);
|
request.setMethod(method);
|
||||||
request.setRequestURI(null);
|
request.setRequestURI(null);
|
||||||
|
|
||||||
request.setServletPath(path);
|
request.setServletPath(path);
|
||||||
|
|
||||||
return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain());
|
return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||||
|
@ -105,27 +105,44 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
|
|||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="newFilterChainProxyNonNamespace" class="org.springframework.security.web.FilterChainProxy">
|
<bean id="newFilterChainProxyNonNamespace" class="org.springframework.security.web.FilterChainProxy">
|
||||||
<property name="matcher">
|
|
||||||
<bean class="org.springframework.security.web.util.AntUrlPathMatcher"/>
|
|
||||||
</property>
|
|
||||||
<property name="filterChainMap">
|
<property name="filterChainMap">
|
||||||
<map>
|
<map>
|
||||||
<entry key="/foo/**">
|
<entry>
|
||||||
|
<key>
|
||||||
|
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
|
||||||
|
<constructor-arg value="/foo/**"/>
|
||||||
|
</bean>
|
||||||
|
</key>
|
||||||
<list>
|
<list>
|
||||||
<ref local="mockFilter"/>
|
<ref local="mockFilter"/>
|
||||||
</list>
|
</list>
|
||||||
</entry>
|
</entry>
|
||||||
<entry key="/some/other/path/**">
|
<entry>
|
||||||
|
<key>
|
||||||
|
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
|
||||||
|
<constructor-arg value="/some/other/path/**"/>
|
||||||
|
</bean>
|
||||||
|
</key>
|
||||||
<list>
|
<list>
|
||||||
<ref local="sif"/>
|
<ref local="sif"/>
|
||||||
<ref local="mockFilter"/>
|
<ref local="mockFilter"/>
|
||||||
<ref local="mockFilter2"/>
|
<ref local="mockFilter2"/>
|
||||||
</list>
|
</list>
|
||||||
</entry>
|
</entry>
|
||||||
<entry key="/do/not/filter">
|
<entry>
|
||||||
|
<key>
|
||||||
|
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
|
||||||
|
<constructor-arg value="/do/not/filter*"/>
|
||||||
|
</bean>
|
||||||
|
</key>
|
||||||
<list/>
|
<list/>
|
||||||
</entry>
|
</entry>
|
||||||
<entry key="/**">
|
<entry>
|
||||||
|
<key>
|
||||||
|
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
|
||||||
|
<constructor-arg value="/**"/>
|
||||||
|
</bean>
|
||||||
|
</key>
|
||||||
<list>
|
<list>
|
||||||
<ref local="sif"/>
|
<ref local="sif"/>
|
||||||
<ref local="apf"/>
|
<ref local="apf"/>
|
||||||
|
@ -26,16 +26,15 @@ import java.util.Set;
|
|||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.FilterConfig;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.util.AnyRequestMatcher;
|
||||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
import org.springframework.security.web.util.UrlMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
import org.springframework.web.filter.GenericFilterBean;
|
||||||
@ -45,26 +44,22 @@ import org.springframework.web.filter.GenericFilterBean;
|
|||||||
* Delegates <code>Filter</code> requests to a list of Spring-managed beans.
|
* Delegates <code>Filter</code> requests to a list of Spring-managed beans.
|
||||||
* As of version 2.0, you shouldn't need to explicitly configure a <tt>FilterChainProxy</tt> bean in your application
|
* As of version 2.0, you shouldn't need to explicitly configure a <tt>FilterChainProxy</tt> bean in your application
|
||||||
* context unless you need very fine control over the filter chain contents. Most cases should be adequately covered
|
* context unless you need very fine control over the filter chain contents. Most cases should be adequately covered
|
||||||
* by the default <tt><security:http /></tt> namespace configuration options.
|
* by the default <tt><security:http /></tt> namespace configuration options.
|
||||||
*
|
* <p>
|
||||||
* <p>The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
|
* The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
|
||||||
* <code>web.xml</code>. <code>FilterChainProxy</code> will then pass {@link #init(FilterConfig)}, {@link #destroy()}
|
* <code>web.xml</code>.
|
||||||
* and {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} invocations through to each <code>Filter</code>
|
* <p>
|
||||||
* defined against <code>FilterChainProxy</code>.
|
* As of version 3.1, <tt>FilterChainProxy</tt> is configured using an ordered Map of {@link RequestMatcher} instances
|
||||||
*
|
* to <tt>List</tt>s of <tt>Filter</tt>s. The Map instance will normally be created while parsing the namespace
|
||||||
* <p>As of version 2.0, <tt>FilterChainProxy</tt> is configured using an ordered Map of path patterns to <tt>List</tt>s
|
* configuration, so doesn't have to be set explicitly. Instead the <filter-chain-map> element should be used
|
||||||
* of <tt>Filter</tt> objects. In previous
|
* within the FilterChainProxy bean declaration.
|
||||||
* versions, a {@link FilterInvocationSecurityMetadataSource} was used. This is now deprecated in favour of namespace-based
|
|
||||||
* configuration which provides a more robust and simplfied syntax. The Map instance will normally be
|
|
||||||
* created while parsing the namespace configuration, so doesn't have to be set explicitly.
|
|
||||||
* Instead the <filter-chain-map> element should be used within the FilterChainProxy bean declaration.
|
|
||||||
* This in turn should have a list of child <filter-chain> elements which each define a URI pattern and the list
|
* This in turn should have a list of child <filter-chain> elements which each define a URI pattern and the list
|
||||||
* of filters (as comma-separated bean names) which should be applied to requests which match the pattern.
|
* of filters (as comma-separated bean names) which should be applied to requests which match the pattern.
|
||||||
* An example configuration might look like this:
|
* An example configuration might look like this:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
|
<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
|
||||||
<security:filter-chain-map pathType="ant">
|
<security:filter-chain-map request-matcher="ant">
|
||||||
<security:filter-chain pattern="/do/not/filter" filters="none"/>
|
<security:filter-chain pattern="/do/not/filter" filters="none"/>
|
||||||
<security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
|
<security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
|
||||||
</security:filter-chain-map>
|
</security:filter-chain-map>
|
||||||
@ -73,30 +68,24 @@ import org.springframework.web.filter.GenericFilterBean;
|
|||||||
*
|
*
|
||||||
* The names "filter1", "filter2", "filter3" should be the bean names of <tt>Filter</tt> instances defined in the
|
* The names "filter1", "filter2", "filter3" should be the bean names of <tt>Filter</tt> instances defined in the
|
||||||
* application context. The order of the names defines the order in which the filters will be applied. As shown above,
|
* application context. The order of the names defines the order in which the filters will be applied. As shown above,
|
||||||
* use of the value "none" for the "filters" can be used to exclude
|
* use of the value "none" for the "filters" can be used to exclude a request pattern from the security filter chain
|
||||||
* Please consult the security namespace schema file for a full list of available configuration options.
|
* entirely. Please consult the security namespace schema file for a full list of available configuration options.
|
||||||
*
|
|
||||||
* <p>
|
* <p>
|
||||||
* Each possible URI pattern that <code>FilterChainProxy</code> should service must be entered.
|
* Each possible pattern that <code>FilterChainProxy</code> should service must be entered.
|
||||||
* The first matching URI pattern for a given request will be used to define all of the
|
* The first match for a given request will be used to define all of the <code>Filter</code>s that apply to that
|
||||||
* <code>Filter</code>s that apply to that request. NB: This means you must put most specific URI patterns at the top
|
* request. This means you must put most specific matches at the top of the list, and ensure all <code>Filter</code>s
|
||||||
* of the list, and ensure all <code>Filter</code>s that should apply for a given URI pattern are entered against the
|
* that should apply for a given matcher are entered against the respective entry.
|
||||||
* respective entry. The <code>FilterChainProxy</code> will not iterate the remainder of the URI patterns to locate
|
* The <code>FilterChainProxy</code> will not iterate through the remainder of the map entries to locate additional
|
||||||
* additional <code>Filter</code>s.
|
* <code>Filter</code>s.
|
||||||
*
|
* <p>
|
||||||
* <p><code>FilterChainProxy</code> respects normal handling of <code>Filter</code>s that elect not to call {@link
|
* <code>FilterChainProxy</code> respects normal handling of <code>Filter</code>s that elect not to call {@link
|
||||||
* javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
|
* javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
|
||||||
* javax.servlet.FilterChain)}, in that the remainder of the original or <code>FilterChainProxy</code>-declared filter
|
* javax.servlet.FilterChain)}, in that the remainder of the original or <code>FilterChainProxy</code>-declared filter
|
||||||
* chain will not be called.
|
* chain will not be called.
|
||||||
*
|
* <p>
|
||||||
* <p>Note the <code>Filter</code> lifecycle mismatch between the servlet container and IoC
|
* Note the <code>Filter</code> lifecycle mismatch between the servlet container and IoC
|
||||||
* container. As described in the {@link DelegatingFilterProxy} JavaDocs, we recommend you allow the IoC
|
* container. As described in the {@link DelegatingFilterProxy} JavaDocs, we recommend you allow the IoC
|
||||||
* container to manage the lifecycle instead of the servlet container. By default the <code>DelegatingFilterProxy</code>
|
* container to manage the lifecycle instead of the servlet container.
|
||||||
* will never call this class' {@link #init(FilterConfig)} and {@link #destroy()} methods, which in turns means that
|
|
||||||
* the corresponding methods on the filter beans managed by this class will never be called. If you do need your filters to be
|
|
||||||
* initialized and destroyed, please set the <tt>targetFilterLifecycle</tt> initialization parameter against the
|
|
||||||
* <code>DelegatingFilterProxy</code> to specify that servlet container lifecycle management should be used. You don't
|
|
||||||
* need to worry about this in most cases.
|
|
||||||
*
|
*
|
||||||
* @author Carlos Sanchez
|
* @author Carlos Sanchez
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
@ -111,20 +100,15 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
//~ Instance fields ================================================================================================
|
||||||
|
|
||||||
// private ApplicationContext applicationContext;
|
private Map<RequestMatcher, List<Filter>> filterChainMap;
|
||||||
/** Map of the original pattern Strings to filter chains */
|
|
||||||
private Map<String, List<Filter>> uncompiledFilterChainMap;
|
|
||||||
/** Compiled pattern version of the filter chain map */
|
|
||||||
private Map<Object, List<Filter>> filterChainMap;
|
|
||||||
private UrlMatcher matcher = new AntUrlPathMatcher();
|
|
||||||
private boolean stripQueryStringFromUrls = true;
|
|
||||||
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
|
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
Assert.notNull(uncompiledFilterChainMap, "filterChainMap must be set");
|
Assert.notNull(filterChainMap, "filterChainMap must be set");
|
||||||
filterChainValidator.validate(this);
|
filterChainValidator.validate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +116,7 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
FilterInvocation fi = new FilterInvocation(request, response, chain);
|
FilterInvocation fi = new FilterInvocation(request, response, chain);
|
||||||
List<Filter> filters = getFilters(fi.getRequestUrl());
|
List<Filter> filters = getFilters(fi.getRequest());
|
||||||
|
|
||||||
if (filters == null || filters.size() == 0) {
|
if (filters == null || filters.size() == 0) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
@ -149,40 +133,18 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
|
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first filter chain matching the supplied URL.
|
* Returns the first filter chain matching the supplied URL.
|
||||||
*
|
*
|
||||||
* @param url the request URL
|
* @param request the request to match
|
||||||
* @return an ordered array of Filters defining the filter chain
|
* @return an ordered array of Filters defining the filter chain
|
||||||
*/
|
*/
|
||||||
public List<Filter> getFilters(String url) {
|
private List<Filter> getFilters(HttpServletRequest request) {
|
||||||
if (stripQueryStringFromUrls) {
|
for (Map.Entry<RequestMatcher, List<Filter>> entry : filterChainMap.entrySet()) {
|
||||||
// String query string - see SEC-953
|
RequestMatcher matcher = entry.getKey();
|
||||||
int firstQuestionMarkIndex = url.indexOf("?");
|
|
||||||
|
|
||||||
if (firstQuestionMarkIndex != -1) {
|
if (matcher.matches(request)) {
|
||||||
url = url.substring(0, firstQuestionMarkIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<Object, List<Filter>> entry : filterChainMap.entrySet()) {
|
|
||||||
Object path = entry.getKey();
|
|
||||||
|
|
||||||
if (matcher.requiresLowerCaseUrl()) {
|
|
||||||
url = url.toLowerCase();
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean matched = matcher.pathMatchesUrl(path, url);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
return entry.getValue();
|
return entry.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +152,16 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method, mainly for testing.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return matching filter list
|
||||||
|
*/
|
||||||
|
public List<Filter> getFilters(String url) {
|
||||||
|
return getFilters(new FilterInvocation(url, null).getRequest());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains all of the <b>unique</b><code>Filter</code> instances registered in the map of
|
* Obtains all of the <b>unique</b><code>Filter</code> instances registered in the map of
|
||||||
* filter chains.
|
* filter chains.
|
||||||
@ -225,15 +197,14 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void setFilterChainMap(Map filterChainMap) {
|
public void setFilterChainMap(Map filterChainMap) {
|
||||||
checkContents(filterChainMap);
|
checkContents(filterChainMap);
|
||||||
uncompiledFilterChainMap = new LinkedHashMap<String, List<Filter>>(filterChainMap);
|
this.filterChainMap = new LinkedHashMap<RequestMatcher, List<Filter>>(filterChainMap);
|
||||||
checkPathOrder();
|
checkPathOrder();
|
||||||
createCompiledMap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void checkContents(Map filterChainMap) {
|
private void checkContents(Map filterChainMap) {
|
||||||
for (Object key : filterChainMap.keySet()) {
|
for (Object key : filterChainMap.keySet()) {
|
||||||
Assert.isInstanceOf(String.class, key, "Path key must be a String but found " + key);
|
Assert.isInstanceOf(RequestMatcher.class, key, "Path key must be a RequestMatcher but found " + key);
|
||||||
Object filters = filterChainMap.get(key);
|
Object filters = filterChainMap.get(key);
|
||||||
Assert.isInstanceOf(List.class, filters, "Value must be a filter list");
|
Assert.isInstanceOf(List.class, filters, "Value must be a filter list");
|
||||||
// Check the contents
|
// Check the contents
|
||||||
@ -248,50 +219,25 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
|
|
||||||
private void checkPathOrder() {
|
private void checkPathOrder() {
|
||||||
// Check that the universal pattern is listed at the end, if at all
|
// Check that the universal pattern is listed at the end, if at all
|
||||||
String[] paths = (String[]) uncompiledFilterChainMap.keySet().toArray(new String[0]);
|
RequestMatcher[] matchers = filterChainMap.keySet().toArray(new RequestMatcher[0]);
|
||||||
String universalMatch = matcher.getUniversalMatchPattern();
|
|
||||||
|
|
||||||
for (int i=0; i < paths.length-1; i++) {
|
for (int i=0; i < matchers.length-1; i++) {
|
||||||
if (paths[i].equals(universalMatch)) {
|
if (matchers[i] instanceof AnyRequestMatcher) {
|
||||||
throw new IllegalArgumentException("A universal match pattern " + universalMatch + " is defined " +
|
throw new IllegalArgumentException("A universal match pattern ('/**') is defined " +
|
||||||
" before other patterns in the filter chain, causing them to be ignored. Please check the " +
|
" before other patterns in the filter chain, causing them to be ignored. Please check the " +
|
||||||
"ordering in your <security:http> namespace or FilterChainProxy bean configuration");
|
"ordering in your <security:http> namespace or FilterChainProxy bean configuration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createCompiledMap() {
|
|
||||||
filterChainMap = new LinkedHashMap<Object, List<Filter>>(uncompiledFilterChainMap.size());
|
|
||||||
|
|
||||||
for (String path : uncompiledFilterChainMap.keySet()) {
|
|
||||||
filterChainMap.put(matcher.compile(path), uncompiledFilterChainMap.get(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of the underlying filter chain map. Modifications to the map contents
|
* Returns a copy of the underlying filter chain map. Modifications to the map contents
|
||||||
* will not affect the FilterChainProxy state - to change the map call <tt>setFilterChainMap</tt>.
|
* will not affect the FilterChainProxy state - to change the map call <tt>setFilterChainMap</tt>.
|
||||||
*
|
*
|
||||||
* @return the map of path pattern Strings to filter chain lists (with ordering guaranteed).
|
* @return the map of path pattern Strings to filter chain lists (with ordering guaranteed).
|
||||||
*/
|
*/
|
||||||
public Map<String, List<Filter>> getFilterChainMap() {
|
public Map<RequestMatcher, List<Filter>> getFilterChainMap() {
|
||||||
return new LinkedHashMap<String, List<Filter>>(uncompiledFilterChainMap);
|
return new LinkedHashMap<RequestMatcher, List<Filter>>(filterChainMap);
|
||||||
}
|
|
||||||
|
|
||||||
public void setMatcher(UrlMatcher matcher) {
|
|
||||||
this.matcher = matcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UrlMatcher getMatcher() {
|
|
||||||
return matcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set to 'true', the query string will be stripped from the request URL before
|
|
||||||
* attempting to find a matching filter chain. This is the default value.
|
|
||||||
*/
|
|
||||||
public void setStripQueryStringFromUrls(boolean stripQueryStringFromUrls) {
|
|
||||||
this.stripQueryStringFromUrls = stripQueryStringFromUrls;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,9 +252,8 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("FilterChainProxy[");
|
sb.append("FilterChainProxy[");
|
||||||
sb.append(" UrlMatcher = ").append(matcher);
|
sb.append("Filter Chains: ");
|
||||||
sb.append("; Filter Chains: ");
|
sb.append(filterChainMap);
|
||||||
sb.append(uncompiledFilterChainMap);
|
|
||||||
sb.append("]");
|
sb.append("]");
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
|
@ -15,13 +15,28 @@
|
|||||||
|
|
||||||
package org.springframework.security.web;
|
package org.springframework.security.web;
|
||||||
|
|
||||||
import org.springframework.security.web.util.UrlUtils;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +51,15 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
* @author colin sampaleanu
|
* @author colin sampaleanu
|
||||||
*/
|
*/
|
||||||
public class FilterInvocation {
|
public class FilterInvocation {
|
||||||
|
//~ Static fields ==================================================================================================
|
||||||
|
static final FilterChain DUMMY_CHAIN = new FilterChain() {
|
||||||
|
public void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException {
|
||||||
|
throw new UnsupportedOperationException("Dummy filter chain");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final HttpServletResponse DUMMY_RESPONSE = new DummyResponse();
|
||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
//~ Instance fields ================================================================================================
|
||||||
|
|
||||||
private FilterChain chain;
|
private FilterChain chain;
|
||||||
@ -54,6 +78,26 @@ public class FilterInvocation {
|
|||||||
this.chain = chain;
|
this.chain = chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FilterInvocation(String servletPath, String method) {
|
||||||
|
this(null, servletPath, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterInvocation(String contextPath, String servletPath, String method) {
|
||||||
|
this(contextPath, servletPath, null, null, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterInvocation(String contextPath, String servletPath, String pathInfo, String query, String method) {
|
||||||
|
DummyRequest request = new DummyRequest();
|
||||||
|
if (contextPath == null) {
|
||||||
|
contextPath = "/cp";
|
||||||
|
}
|
||||||
|
request.setContextPath(contextPath);
|
||||||
|
request.setServletPath(servletPath);
|
||||||
|
request.setPathInfo(pathInfo);
|
||||||
|
request.setMethod(method);
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
|
|
||||||
public FilterChain getChain() {
|
public FilterChain getChain() {
|
||||||
@ -101,3 +145,386 @@ public class FilterInvocation {
|
|||||||
return "FilterInvocation: URL: " + getRequestUrl();
|
return "FilterInvocation: URL: " + getRequestUrl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
class DummyRequest implements HttpServletRequest {
|
||||||
|
private String requestURI;
|
||||||
|
private String contextPath = "";
|
||||||
|
private String servletPath;
|
||||||
|
private String pathInfo;
|
||||||
|
private String queryString;
|
||||||
|
private String method;
|
||||||
|
|
||||||
|
public void setRequestURI(String requestURI) {
|
||||||
|
this.requestURI = requestURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPathInfo(String pathInfo) {
|
||||||
|
this.pathInfo = pathInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestURI() {
|
||||||
|
return requestURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContextPath(String contextPath) {
|
||||||
|
this.contextPath = contextPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContextPath() {
|
||||||
|
return contextPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServletPath(String servletPath) {
|
||||||
|
this.servletPath = servletPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServletPath() {
|
||||||
|
return servletPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPathInfo() {
|
||||||
|
return pathInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getAuthType() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cookie[] getCookies() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDateHeader(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getHeaderNames() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getHeaders(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntHeader(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPathTranslated() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemoteUser() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuffer getRequestURL() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestedSessionId() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpSession getSession() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpSession getSession(boolean create) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Principal getUserPrincipal() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRequestedSessionIdFromCookie() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRequestedSessionIdFromURL() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRequestedSessionIdFromUrl() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRequestedSessionIdValid() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserInRole(String role) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getAttribute(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getAttributeNames() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCharacterEncoding() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getContentLength() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServletInputStream getInputStream() throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalAddr() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalName() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalPort() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Locale getLocale() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getLocales() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParameter(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map getParameterMap() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getParameterNames() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getParameterValues(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedReader getReader() throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealPath(String path) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemoteAddr() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemoteHost() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemotePort() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestDispatcher getRequestDispatcher(String path) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheme() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerName() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getServerPort() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecure() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttribute(String name, Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyResponse implements HttpServletResponse {
|
||||||
|
public void addCookie(Cookie cookie) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDateHeader(String name, long date) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(String name, String value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addIntHeader(String name, int value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsHeader(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encodeRedirectURL(String url) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encodeRedirectUrl(String url) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encodeURL(String url) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encodeUrl(String url) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendError(int sc) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendError(int sc, String msg) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendRedirect(String location) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDateHeader(String name, long date) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeader(String name, String value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIntHeader(String name, int value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(int sc) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(int sc, String sm) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushBuffer() throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferSize() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCharacterEncoding() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Locale getLocale() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServletOutputStream getOutputStream() throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrintWriter getWriter() throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCommitted() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetBuffer() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBufferSize(int size) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCharacterEncoding(String charset) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentLength(int len) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentType(String type) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocale(Locale loc) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,27 +15,7 @@
|
|||||||
|
|
||||||
package org.springframework.security.web.access;
|
package org.springframework.security.web.access;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.RequestDispatcher;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletInputStream;
|
|
||||||
import javax.servlet.ServletOutputStream;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
@ -59,14 +39,6 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv
|
|||||||
|
|
||||||
protected static final Log logger = LogFactory.getLog(DefaultWebInvocationPrivilegeEvaluator.class);
|
protected static final Log logger = LogFactory.getLog(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||||
|
|
||||||
static final FilterChain DUMMY_CHAIN = new FilterChain() {
|
|
||||||
public void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException {
|
|
||||||
throw new UnsupportedOperationException("DefaultWebInvocationPrivilegeEvaluator does not support filter chains");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static final HttpServletResponse DUMMY_RESPONSE = new DummyResponse();
|
|
||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
//~ Instance fields ================================================================================================
|
||||||
|
|
||||||
private AbstractSecurityInterceptor securityInterceptor;
|
private AbstractSecurityInterceptor securityInterceptor;
|
||||||
@ -114,11 +86,7 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv
|
|||||||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
||||||
Assert.notNull(uri, "uri parameter is required");
|
Assert.notNull(uri, "uri parameter is required");
|
||||||
|
|
||||||
if (contextPath == null) {
|
FilterInvocation fi = new FilterInvocation(contextPath, uri, method);
|
||||||
contextPath = "/ctxpath";
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation(contextPath, uri, method);
|
|
||||||
Collection<ConfigAttribute> attrs = securityInterceptor.obtainSecurityMetadataSource().getAttributes(fi);
|
Collection<ConfigAttribute> attrs = securityInterceptor.obtainSecurityMetadataSource().getAttributes(fi);
|
||||||
|
|
||||||
if (attrs == null) {
|
if (attrs == null) {
|
||||||
@ -145,389 +113,6 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FilterInvocation createFilterInvocation(String contextPath, String uri, String method) {
|
|
||||||
Assert.hasText(uri, "URI required");
|
|
||||||
|
|
||||||
DummyRequest req = new DummyRequest();
|
|
||||||
req.setRequestURI(contextPath + uri);
|
|
||||||
req.setContextPath(contextPath);
|
|
||||||
req.setServletPath(null);
|
|
||||||
req.setMethod(method);
|
|
||||||
|
|
||||||
return new FilterInvocation(req, DUMMY_RESPONSE, DUMMY_CHAIN);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
class DummyRequest implements HttpServletRequest {
|
|
||||||
private String requestURI;
|
|
||||||
private String contextPath = "";
|
|
||||||
private String servletPath;
|
|
||||||
private String method;
|
|
||||||
|
|
||||||
public void setRequestURI(String requestURI) {
|
|
||||||
this.requestURI = requestURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestURI() {
|
|
||||||
return requestURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContextPath(String contextPath) {
|
|
||||||
this.contextPath = contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContextPath() {
|
|
||||||
return contextPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServletPath(String servletPath) {
|
|
||||||
this.servletPath = servletPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getServletPath() {
|
|
||||||
return servletPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMethod(String method) {
|
|
||||||
this.method = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMethod() {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPathInfo() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQueryString() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthType() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cookie[] getCookies() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDateHeader(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHeader(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Enumeration getHeaderNames() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Enumeration getHeaders(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIntHeader(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPathTranslated() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRemoteUser() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringBuffer getRequestURL() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestedSessionId() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpSession getSession() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpSession getSession(boolean create) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Principal getUserPrincipal() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRequestedSessionIdFromCookie() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRequestedSessionIdFromURL() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRequestedSessionIdFromUrl() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRequestedSessionIdValid() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUserInRole(String role) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getAttribute(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Enumeration getAttributeNames() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCharacterEncoding() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getContentLength() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLocalAddr() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLocalName() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLocalPort() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Locale getLocale() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Enumeration getLocales() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getParameter(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map getParameterMap() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Enumeration getParameterNames() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getParameterValues(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProtocol() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferedReader getReader() throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealPath(String path) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRemoteAddr() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRemoteHost() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRemotePort() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestDispatcher getRequestDispatcher(String path) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getScheme() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getServerName() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getServerPort() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSecure() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeAttribute(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAttribute(String name, Object o) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DummyResponse implements HttpServletResponse {
|
|
||||||
public void addCookie(Cookie cookie) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDateHeader(String name, long date) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addHeader(String name, String value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addIntHeader(String name, int value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsHeader(String name) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encodeRedirectURL(String url) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encodeRedirectUrl(String url) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encodeURL(String url) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encodeUrl(String url) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendError(int sc) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendError(int sc, String msg) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendRedirect(String location) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDateHeader(String name, long date) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, String value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIntHeader(String name, int value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(int sc) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(int sc, String sm) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flushBuffer() throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBufferSize() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCharacterEncoding() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Locale getLocale() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServletOutputStream getOutputStream() throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrintWriter getWriter() throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCommitted() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetBuffer() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBufferSize(int size) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCharacterEncoding(String charset) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContentLength(int len) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContentType(String type) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocale(Locale loc) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -11,8 +11,7 @@ import org.springframework.expression.ExpressionParser;
|
|||||||
import org.springframework.expression.ParseException;
|
import org.springframework.expression.ParseException;
|
||||||
import org.springframework.security.access.ConfigAttribute;
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.RequestKey;
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
import org.springframework.security.web.util.UrlMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,21 +23,22 @@ import org.springframework.util.Assert;
|
|||||||
public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
|
public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
|
||||||
private final static Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
|
private final static Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
|
||||||
|
|
||||||
public ExpressionBasedFilterInvocationSecurityMetadataSource(UrlMatcher urlMatcher,
|
public ExpressionBasedFilterInvocationSecurityMetadataSource(
|
||||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestMap, WebSecurityExpressionHandler expressionHandler) {
|
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap,
|
||||||
super(urlMatcher, processMap(requestMap, expressionHandler.getExpressionParser()));
|
WebSecurityExpressionHandler expressionHandler) {
|
||||||
|
super(processMap(requestMap, expressionHandler.getExpressionParser()));
|
||||||
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
|
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkedHashMap<RequestKey, Collection<ConfigAttribute>> processMap(
|
private static LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processMap(
|
||||||
LinkedHashMap<RequestKey,Collection<ConfigAttribute>> requestMap, ExpressionParser parser) {
|
LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>> requestMap, ExpressionParser parser) {
|
||||||
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
|
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
|
||||||
|
|
||||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestToExpressionAttributesMap =
|
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestToExpressionAttributesMap =
|
||||||
new LinkedHashMap<RequestKey, Collection<ConfigAttribute>>(requestMap);
|
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>(requestMap);
|
||||||
|
|
||||||
for (Map.Entry<RequestKey, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||||
RequestKey request = entry.getKey();
|
RequestMatcher request = entry.getKey();
|
||||||
Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request);
|
Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request);
|
||||||
ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
|
ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
|
||||||
String expression = entry.getValue().toArray(new ConfigAttribute[1])[0].getAttribute();
|
String expression = entry.getValue().toArray(new ConfigAttribute[1])[0].getAttribute();
|
||||||
|
@ -15,192 +15,75 @@
|
|||||||
|
|
||||||
package org.springframework.security.web.access.intercept;
|
package org.springframework.security.web.access.intercept;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.security.access.ConfigAttribute;
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
import org.springframework.security.web.FilterInvocation;
|
import org.springframework.security.web.FilterInvocation;
|
||||||
import org.springframework.security.web.util.UrlMatcher;
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of <tt>FilterInvocationDefinitionSource</tt>.
|
* Default implementation of <tt>FilterInvocationDefinitionSource</tt>.
|
||||||
* <p>
|
* <p>
|
||||||
* Stores an ordered map of compiled URL paths to <tt>ConfigAttribute</tt> lists and provides URL matching
|
* Stores an ordered map of {@link RequestMatcher}s to <tt>ConfigAttribute</tt> collections and provides matching
|
||||||
* against the items stored in this map using the configured <tt>UrlMatcher</tt>.
|
* of {@code FilterInvocation}s against the items stored in the map.
|
||||||
* <p>
|
* <p>
|
||||||
* The order of the URL paths in the map is very important.
|
* The order of the {@link RequestMatcher}s in the map is very important. The <b>first</b> one which matches the
|
||||||
* The system will identify the <b>first</b> matching path for a given HTTP URL. It will not proceed to evaluate
|
* request will be used. Later matchers in the map will not be invoked if a match has already been found.
|
||||||
* later paths if a match has already been found. Accordingly, the most specific matches should be
|
* Accordingly, the most specific matchers should be registered first, with the most general matches registered last.
|
||||||
* registered first, with the most general matches registered last.
|
|
||||||
* <p>
|
* <p>
|
||||||
* If URL paths are registered for a particular HTTP method using, then the method-specific matches will take
|
* The most common method creating an instance is using the Spring Security namespace. For example, the {@code pattern}
|
||||||
* precedence over any URLs which are registered without an HTTP method.
|
* and {@code access} attributes of the {@code <intercept-url>} elements defined as children of the
|
||||||
|
* {@code <http>} element are combined to build the instance used by the {@code FilterSecurityInterceptor}.
|
||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @author Luke Taylor
|
* @author Luke Taylor
|
||||||
*/
|
*/
|
||||||
public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
|
public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
|
||||||
|
|
||||||
private static final Set<String> HTTP_METHODS = new HashSet<String>(Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "TRACE"));
|
|
||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
//private Map<Object, List<ConfigAttribute>> requestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
|
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
|
||||||
/** Stores request maps keyed by specific HTTP methods. A null key matches any method */
|
|
||||||
private Map<String, Map<Object, Collection<ConfigAttribute>>> httpMethodMap =
|
|
||||||
new HashMap<String, Map<Object, Collection<ConfigAttribute>>>();
|
|
||||||
|
|
||||||
private UrlMatcher urlMatcher;
|
|
||||||
|
|
||||||
private boolean stripQueryStringFromUrls;
|
|
||||||
|
|
||||||
//~ Constructors ===================================================================================================
|
//~ Constructors ===================================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the internal request map from the supplied map. The key elements should be of type {@link RequestKey},
|
* Sets the internal request map from the supplied map. The key elements should be of type {@link RequestMatcher},
|
||||||
* which contains a URL path and an optional HTTP method (may be null). The path stored in the key will depend on
|
* which. The path stored in the key will depend on
|
||||||
* the type of the supplied UrlMatcher.
|
* the type of the supplied UrlMatcher.
|
||||||
*
|
*
|
||||||
* @param urlMatcher typically an ant or regular expression matcher.
|
|
||||||
* @param requestMap order-preserving map of request definitions to attribute lists
|
* @param requestMap order-preserving map of request definitions to attribute lists
|
||||||
*/
|
*/
|
||||||
public DefaultFilterInvocationSecurityMetadataSource(UrlMatcher urlMatcher,
|
public DefaultFilterInvocationSecurityMetadataSource(
|
||||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestMap) {
|
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
|
||||||
this.urlMatcher = urlMatcher;
|
|
||||||
|
|
||||||
for (Map.Entry<RequestKey, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
this.requestMap = requestMap;
|
||||||
addSecureUrl(entry.getKey().getUrl(), entry.getKey().getMethod(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a URL,attribute-list pair to the request map, first allowing the <tt>UrlMatcher</tt> to
|
|
||||||
* process the pattern if required, using its <tt>compile</tt> method. The returned object will be used as the key
|
|
||||||
* to the request map and will be passed back to the <tt>UrlMatcher</tt> when iterating through the map to find
|
|
||||||
* a match for a particular URL.
|
|
||||||
*/
|
|
||||||
private void addSecureUrl(String pattern, String method, Collection<ConfigAttribute> attrs) {
|
|
||||||
Map<Object, Collection<ConfigAttribute>> mapToUse = getRequestMapForHttpMethod(method);
|
|
||||||
|
|
||||||
mapToUse.put(urlMatcher.compile(pattern), attrs);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Added URL pattern: " + pattern + "; attributes: " + attrs +
|
|
||||||
(method == null ? "" : " for HTTP method '" + method + "'"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the HTTP method specific request map, creating it if it doesn't already exist.
|
|
||||||
* @param method GET, POST etc
|
|
||||||
* @return map of URL patterns to <tt>ConfigAttribute</tt>s for this method.
|
|
||||||
*/
|
|
||||||
private Map<Object, Collection<ConfigAttribute>> getRequestMapForHttpMethod(String method) {
|
|
||||||
if (method != null && !HTTP_METHODS.contains(method)) {
|
|
||||||
throw new IllegalArgumentException("Unrecognised HTTP method: '" + method + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<Object, Collection<ConfigAttribute>> methodRequestMap = httpMethodMap.get(method);
|
|
||||||
|
|
||||||
if (methodRequestMap == null) {
|
|
||||||
methodRequestMap = new LinkedHashMap<Object, Collection<ConfigAttribute>>();
|
|
||||||
httpMethodMap.put(method, methodRequestMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return methodRequestMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<ConfigAttribute> getAllConfigAttributes() {
|
public Collection<ConfigAttribute> getAllConfigAttributes() {
|
||||||
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
|
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
|
||||||
|
|
||||||
for (Map.Entry<String, Map<Object, Collection<ConfigAttribute>>> entry : httpMethodMap.entrySet()) {
|
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||||
for (Collection<ConfigAttribute> attrs : entry.getValue().values()) {
|
allAttributes.addAll(entry.getValue());
|
||||||
allAttributes.addAll(attrs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return allAttributes;
|
return allAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Collection<ConfigAttribute> getAttributes(Object object) {
|
public Collection<ConfigAttribute> getAttributes(Object object) {
|
||||||
if ((object == null) || !this.supports(object.getClass())) {
|
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
|
||||||
throw new IllegalArgumentException("Object must be a FilterInvocation");
|
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||||
}
|
if (entry.getKey().matches(request)) {
|
||||||
|
|
||||||
String url = ((FilterInvocation) object).getRequestUrl();
|
|
||||||
String method = ((FilterInvocation) object).getHttpRequest().getMethod();
|
|
||||||
|
|
||||||
return lookupAttributes(url, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the actual lookup of the relevant <tt>ConfigAttribute</tt>s for the given <code>FilterInvocation</code>.
|
|
||||||
* <p>
|
|
||||||
* By default, iterates through the stored URL map and calls the
|
|
||||||
* {@link UrlMatcher#pathMatchesUrl(Object path, String url)} method until a match is found.
|
|
||||||
*
|
|
||||||
* @param url the URI to retrieve configuration attributes for
|
|
||||||
* @param method the HTTP method (GET, POST, DELETE...), or null for any method.
|
|
||||||
*
|
|
||||||
* @return the <code>ConfigAttribute</code>s that apply to the specified <code>FilterInvocation</code>
|
|
||||||
* or null if no match is found
|
|
||||||
*/
|
|
||||||
public final Collection<ConfigAttribute> lookupAttributes(String url, String method) {
|
|
||||||
if (stripQueryStringFromUrls) {
|
|
||||||
// Strip anything after a question mark symbol, as per SEC-161. See also SEC-321
|
|
||||||
int firstQuestionMarkIndex = url.indexOf("?");
|
|
||||||
|
|
||||||
if (firstQuestionMarkIndex != -1) {
|
|
||||||
url = url.substring(0, firstQuestionMarkIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlMatcher.requiresLowerCaseUrl()) {
|
|
||||||
url = url.toLowerCase();
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain the map of request patterns to attributes for this method and lookup the url.
|
|
||||||
Collection<ConfigAttribute> attributes = extractMatchingAttributes(url, httpMethodMap.get(method));
|
|
||||||
|
|
||||||
// If no attributes found in method-specific map, use the general one stored under the null key
|
|
||||||
if (attributes == null) {
|
|
||||||
attributes = extractMatchingAttributes(url, httpMethodMap.get(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<ConfigAttribute> extractMatchingAttributes(String url, Map<Object, Collection<ConfigAttribute>> map) {
|
|
||||||
if (map == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean debug = logger.isDebugEnabled();
|
|
||||||
|
|
||||||
for (Map.Entry<Object, Collection<ConfigAttribute>> entry : map.entrySet()) {
|
|
||||||
Object p = entry.getKey();
|
|
||||||
boolean matched = urlMatcher.pathMatchesUrl(entry.getKey(), url);
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
logger.debug("Candidate is: '" + url + "'; pattern is " + p + "; matched=" + matched);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
return entry.getValue();
|
return entry.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,16 +93,4 @@ public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvo
|
|||||||
public boolean supports(Class<?> clazz) {
|
public boolean supports(Class<?> clazz) {
|
||||||
return FilterInvocation.class.isAssignableFrom(clazz);
|
return FilterInvocation.class.isAssignableFrom(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UrlMatcher getUrlMatcher() {
|
|
||||||
return urlMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConvertUrlToLowercaseBeforeComparison() {
|
|
||||||
return urlMatcher.requiresLowerCaseUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStripQueryStringFromUrls(boolean stripQueryStringFromUrls) {
|
|
||||||
this.stripQueryStringFromUrls = stripQueryStringFromUrls;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package org.springframework.security.web.util;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matcher which compares a pre-defined ant-style pattern against the URL of an
|
||||||
|
* {@code HttpServletRequest}. Ignores the query string of the URL.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @since 3.1
|
||||||
|
*
|
||||||
|
* @see AntPathMatcher
|
||||||
|
*/
|
||||||
|
public final class AntPathRequestMatcher implements RequestMatcher {
|
||||||
|
private final static Log logger = LogFactory.getLog(AntPathRequestMatcher.class);
|
||||||
|
|
||||||
|
private static final AntPathMatcher antMatcher = new AntPathMatcher();
|
||||||
|
|
||||||
|
private final String pattern;
|
||||||
|
private final HttpMethod httpMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a matcher with the specific pattern which will match all HTTP methods.
|
||||||
|
*
|
||||||
|
* @param pattern the ant pattern to use for matching
|
||||||
|
*/
|
||||||
|
public AntPathRequestMatcher(String pattern) {
|
||||||
|
this(pattern, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a matcher with the supplied pattern which will match all HTTP methods.
|
||||||
|
*
|
||||||
|
* @param pattern the ant pattern to use for matching
|
||||||
|
* @param httpMethod the HTTP method. The {@code matches} method will return false if the incoming request doesn't
|
||||||
|
* have the same method.
|
||||||
|
*/
|
||||||
|
public AntPathRequestMatcher(String pattern, String httpMethod) {
|
||||||
|
Assert.hasText(pattern, "Pattern cannot be null or empty");
|
||||||
|
this.pattern = pattern.toLowerCase();
|
||||||
|
this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the configured pattern (and HTTP-Method) match those of the supplied request.
|
||||||
|
*
|
||||||
|
* @param request the request to match against.
|
||||||
|
*/
|
||||||
|
public boolean matches(HttpServletRequest request) {
|
||||||
|
if (httpMethod != null && httpMethod != HttpMethod.valueOf(request.getMethod())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = request.getServletPath();
|
||||||
|
|
||||||
|
if (request.getPathInfo() != null) {
|
||||||
|
url += request.getPathInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
url = url.toLowerCase();
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Checking match of request : '" + url + "'; against '" + pattern + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Optimise, since the pattern is fixed.
|
||||||
|
return antMatcher.match(pattern, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPattern() {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Ant [pattern='").append(pattern).append("'");
|
||||||
|
|
||||||
|
if (httpMethod != null) {
|
||||||
|
sb.append(", " + httpMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("]");
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.springframework.security.web.util;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches any supplied request.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public final class AnyRequestMatcher implements RequestMatcher {
|
||||||
|
|
||||||
|
public boolean matches(HttpServletRequest request) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.springframework.security.web.util;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public final class RegexRequestMatcher implements RequestMatcher {
|
||||||
|
private final static Log logger = LogFactory.getLog(RegexRequestMatcher.class);
|
||||||
|
|
||||||
|
private final Pattern pattern;
|
||||||
|
private final HttpMethod httpMethod;
|
||||||
|
|
||||||
|
public RegexRequestMatcher(String pattern, String httpMethod) {
|
||||||
|
this(pattern, httpMethod, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegexRequestMatcher(String pattern, String httpMethod, boolean caseInsensitive) {
|
||||||
|
if (caseInsensitive) {
|
||||||
|
this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
|
||||||
|
} else {
|
||||||
|
this.pattern = Pattern.compile(pattern);
|
||||||
|
}
|
||||||
|
this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(HttpServletRequest request) {
|
||||||
|
if (httpMethod != null && httpMethod != HttpMethod.valueOf(request.getMethod())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = request.getServletPath();
|
||||||
|
String pathInfo = request.getPathInfo();
|
||||||
|
String query = request.getQueryString();
|
||||||
|
|
||||||
|
if (pathInfo != null || query != null) {
|
||||||
|
StringBuilder sb = new StringBuilder(url);
|
||||||
|
|
||||||
|
if (pathInfo != null) {
|
||||||
|
sb.append(pathInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query != null) {
|
||||||
|
sb.append(query);
|
||||||
|
}
|
||||||
|
url = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Checking match of request : '" + url + "'; against '" + pattern + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern.matcher(url).matches();
|
||||||
|
}
|
||||||
|
}
|
@ -13,17 +13,20 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.security.web.access.intercept;
|
package org.springframework.security.web;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.web.FilterInvocation;
|
import org.springframework.security.web.FilterInvocation;
|
||||||
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FilterInvocation}.
|
* Tests {@link FilterInvocation}.
|
||||||
@ -115,4 +118,18 @@ public class FilterInvocationTests {
|
|||||||
assertEquals("FilterInvocation: URL: /HelloWorld", fi.toString());
|
assertEquals("FilterInvocation: URL: /HelloWorld", fi.toString());
|
||||||
assertEquals("http://www.example.com/mycontext/HelloWorld", fi.getFullRequestUrl());
|
assertEquals("http://www.example.com/mycontext/HelloWorld", fi.getFullRequestUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=UnsupportedOperationException.class)
|
||||||
|
public void dummyChainRejectsInvocation() throws Exception {
|
||||||
|
FilterInvocation.DUMMY_CHAIN.doFilter(mock(HttpServletRequest.class), mock(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dummyRequestIsSupportedByUrlUtils() throws Exception {
|
||||||
|
DummyRequest request = new DummyRequest();
|
||||||
|
request.setContextPath("");
|
||||||
|
request.setRequestURI("/something");
|
||||||
|
UrlUtils.buildRequestUrl(request);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -19,9 +19,6 @@ import static org.junit.Assert.*;
|
|||||||
import static org.mockito.Matchers.*;
|
import static org.mockito.Matchers.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
@ -34,7 +31,6 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.util.UrlUtils;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,17 +98,4 @@ public class DefaultWebInvocationPrivilegeEvaluatorTests {
|
|||||||
|
|
||||||
assertFalse(wipe.isAllowed("/foo/index.jsp", token));
|
assertFalse(wipe.isAllowed("/foo/index.jsp", token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=UnsupportedOperationException.class)
|
|
||||||
public void dummyChainRejectsInvocation() throws Exception {
|
|
||||||
DefaultWebInvocationPrivilegeEvaluator.DUMMY_CHAIN.doFilter(mock(HttpServletRequest.class), mock(HttpServletResponse.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dummyRequestIsSupportedByUrlUtils() throws Exception {
|
|
||||||
DummyRequest request = new DummyRequest();
|
|
||||||
request.setContextPath("");
|
|
||||||
request.setRequestURI("/something");
|
|
||||||
UrlUtils.buildRequestUrl(request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
|||||||
import org.springframework.security.access.ConfigAttribute;
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
import org.springframework.security.access.SecurityConfig;
|
import org.springframework.security.access.SecurityConfig;
|
||||||
import org.springframework.security.web.FilterInvocation;
|
import org.springframework.security.web.FilterInvocation;
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.util.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.access.intercept.RequestKey;
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests parts of {@link DefaultFilterInvocationSecurityMetadataSource} not tested by {@link
|
* Tests parts of {@link DefaultFilterInvocationSecurityMetadataSource} not tested by {@link
|
||||||
@ -39,41 +38,25 @@ import org.springframework.security.web.util.AntUrlPathMatcher;
|
|||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||||
private DefaultFilterInvocationSecurityMetadataSource fids;
|
private DefaultFilterInvocationSecurityMetadataSource fids;
|
||||||
private Collection<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
|
private Collection<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
private void createFids(String url, String method) {
|
private void createFids(String pattern, String method) {
|
||||||
LinkedHashMap requestMap = new LinkedHashMap();
|
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap =
|
||||||
requestMap.put(new RequestKey(url, method), def);
|
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
|
||||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(), requestMap);
|
requestMap.put(new AntPathRequestMatcher(pattern, method), def);
|
||||||
fids.setStripQueryStringFromUrls(true);
|
fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
|
||||||
}
|
|
||||||
|
|
||||||
private void createFids(String url, boolean convertToLowerCase) {
|
|
||||||
LinkedHashMap requestMap = new LinkedHashMap();
|
|
||||||
requestMap.put(new RequestKey(url), def);
|
|
||||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(convertToLowerCase), requestMap);
|
|
||||||
fids.setStripQueryStringFromUrls(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertUrlToLowercaseIsTrueByDefault() {
|
|
||||||
LinkedHashMap requestMap = new LinkedHashMap();
|
|
||||||
requestMap.put(new RequestKey("/something"), def);
|
|
||||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(), requestMap);
|
|
||||||
assertTrue(fids.isConvertUrlToLowercaseBeforeComparison());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
|
public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
|
||||||
createFids("/secure/super/**", null);
|
createFids("/secure/super/**", null);
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null);
|
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null, null, null);
|
||||||
|
|
||||||
assertEquals(def, fids.lookupAttributes(fi.getRequestUrl(), null));
|
assertEquals(def, fids.getAttributes(fi));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,29 +66,19 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
|||||||
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
|
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
|
||||||
createFids("/SeCuRE/super/**", null);
|
createFids("/SeCuRE/super/**", null);
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null);
|
FilterInvocation fi = createFilterInvocation("/secure", "/super/somefile.html", null, null);
|
||||||
|
|
||||||
Collection<ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
Collection<ConfigAttribute> response = fids.getAttributes(fi);
|
||||||
assertEquals(def, response);
|
assertEquals(def, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void lookupRequiringExactMatchFailsIfNotMatching() {
|
|
||||||
createFids("/secure/super/**", false);
|
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/SeCuRE/super/somefile.html", null);
|
|
||||||
|
|
||||||
Collection<ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
|
||||||
assertEquals(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lookupRequiringExactMatchIsSuccessful() {
|
public void lookupRequiringExactMatchIsSuccessful() {
|
||||||
createFids("/SeCurE/super/**", false);
|
createFids("/SeCurE/super/**", null);
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null);
|
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null, null, null);
|
||||||
|
|
||||||
Collection<ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
Collection<ConfigAttribute> response = fids.getAttributes(fi);
|
||||||
assertEquals(def, response);
|
assertEquals(def, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,9 +86,9 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
|||||||
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
|
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
|
||||||
createFids("/someAdminPage.html**", null);
|
createFids("/someAdminPage.html**", null);
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/someAdminPage.html?a=/test", null);
|
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, "a=/test", null);
|
||||||
|
|
||||||
Collection<ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
Collection<ConfigAttribute> response = fids.getAttributes(fi);
|
||||||
assertEquals(def, response); // see SEC-161 (it should truncate after ? sign)
|
assertEquals(def, response); // see SEC-161 (it should truncate after ? sign)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +101,7 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
|||||||
public void httpMethodLookupSucceeds() {
|
public void httpMethodLookupSucceeds() {
|
||||||
createFids("/somepage**", "GET");
|
createFids("/somepage**", "GET");
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/somepage", "GET");
|
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
|
||||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||||
assertEquals(def, attrs);
|
assertEquals(def, attrs);
|
||||||
}
|
}
|
||||||
@ -137,7 +110,7 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
|||||||
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
|
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
|
||||||
createFids("/somepage**", null);
|
createFids("/somepage**", null);
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/somepage", "GET");
|
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
|
||||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||||
assertEquals(def, attrs);
|
assertEquals(def, attrs);
|
||||||
}
|
}
|
||||||
@ -146,50 +119,23 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
|||||||
public void requestWithDifferentHttpMethodDoesntMatch() {
|
public void requestWithDifferentHttpMethodDoesntMatch() {
|
||||||
createFids("/somepage**", "GET");
|
createFids("/somepage**", "GET");
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/somepage", null);
|
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST");
|
||||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||||
assertNull(attrs);
|
assertNull(attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void httpMethodSpecificUrlTakesPrecedence() {
|
|
||||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestKey, Collection<ConfigAttribute>>();
|
|
||||||
// Even though this is added before the Http method-specific def, the latter should match
|
|
||||||
requestMap.put(new RequestKey("/**"), def);
|
|
||||||
Collection<ConfigAttribute> postOnlyDef = SecurityConfig.createList("ROLE_TWO");
|
|
||||||
requestMap.put(new RequestKey("/somepage**", "POST"), postOnlyDef);
|
|
||||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(), requestMap);
|
|
||||||
|
|
||||||
Collection<ConfigAttribute> attrs = fids.getAttributes(createFilterInvocation("/somepage", "POST"));
|
|
||||||
assertEquals(postOnlyDef, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SEC-1236
|
// SEC-1236
|
||||||
@Test
|
@Test
|
||||||
public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() throws Exception {
|
public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() throws Exception {
|
||||||
LinkedHashMap requestMap = new LinkedHashMap();
|
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap =
|
||||||
|
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
|
||||||
Collection<ConfigAttribute> userAttrs = SecurityConfig.createList("A");
|
Collection<ConfigAttribute> userAttrs = SecurityConfig.createList("A");
|
||||||
requestMap.put(new RequestKey("/user/**", null), userAttrs);
|
|
||||||
requestMap.put(new RequestKey("/teller/**", "GET"), SecurityConfig.createList("B"));
|
|
||||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(), requestMap);
|
|
||||||
fids.setStripQueryStringFromUrls(true);
|
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/user", "GET");
|
requestMap.put(new AntPathRequestMatcher("/user/**", null), userAttrs);
|
||||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
requestMap.put(new AntPathRequestMatcher("/teller/**", "GET"), SecurityConfig.createList("B"));
|
||||||
assertEquals(userAttrs, attrs);
|
fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
FilterInvocation fi = createFilterInvocation("/user", null, null, "GET");
|
||||||
public void methodSpecificMatchTakesPrecdenceRegardlessOfOrdering() throws Exception {
|
|
||||||
// Unfortunate but unavoidable
|
|
||||||
LinkedHashMap requestMap = new LinkedHashMap();
|
|
||||||
Collection<ConfigAttribute> userAttrs = SecurityConfig.createList("A");
|
|
||||||
requestMap.put(new RequestKey("/secure/specific.html", null), SecurityConfig.createList("B"));
|
|
||||||
requestMap.put(new RequestKey("/secure/*.html", "GET"), userAttrs);
|
|
||||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(), requestMap);
|
|
||||||
fids.setStripQueryStringFromUrls(true);
|
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/secure/specific.html", "GET");
|
|
||||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||||
assertEquals(userAttrs, attrs);
|
assertEquals(userAttrs, attrs);
|
||||||
}
|
}
|
||||||
@ -201,23 +147,24 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
|||||||
public void extraQuestionMarkStillMatches() {
|
public void extraQuestionMarkStillMatches() {
|
||||||
createFids("/someAdminPage.html*", null);
|
createFids("/someAdminPage.html*", null);
|
||||||
|
|
||||||
FilterInvocation fi = createFilterInvocation("/someAdminPage.html?x=2/aa?y=3", null);
|
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, null, null);
|
||||||
|
|
||||||
Collection<ConfigAttribute> response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
Collection<ConfigAttribute> response = fids.getAttributes(fi);
|
||||||
assertEquals(def, response);
|
assertEquals(def, response);
|
||||||
|
|
||||||
fi = createFilterInvocation("/someAdminPage.html??", null);
|
fi = createFilterInvocation("/someAdminPage.html", null, "?", null);
|
||||||
|
|
||||||
response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
response = fids.getAttributes(fi);
|
||||||
assertEquals(def, response);
|
assertEquals(def, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FilterInvocation createFilterInvocation(String path, String method) {
|
private FilterInvocation createFilterInvocation(String servletPath, String pathInfo, String queryString, String method) {
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.setRequestURI(null);
|
request.setRequestURI(null);
|
||||||
request.setMethod(method);
|
request.setMethod(method);
|
||||||
|
request.setServletPath(servletPath);
|
||||||
request.setServletPath(path);
|
request.setPathInfo(pathInfo);
|
||||||
|
request.setQueryString(queryString);
|
||||||
|
|
||||||
return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class));
|
return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class));
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ Import-Template:
|
|||||||
org.springframework.dao;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
org.springframework.dao;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
||||||
org.springframework.expression;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
org.springframework.expression;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
||||||
org.springframework.expression.spel.*;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
org.springframework.expression.spel.*;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
||||||
|
org.springframework.http.*;version="[${spring.version}, 3.2.0)",
|
||||||
org.springframework.jdbc.*;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
org.springframework.jdbc.*;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
||||||
org.springframework.mock.web;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
org.springframework.mock.web;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
||||||
org.springframework.web.context.*;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
org.springframework.web.context.*;version="[${spring.version}, 3.2.0)";resolution:=optional,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user