1
0
mirror of synced 2026-08-04 17:27:13 +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:
Luke Taylor
2010-03-01 00:59:46 +00:00
parent 962a2d5272
commit 93438defff
23 changed files with 978 additions and 992 deletions
@@ -2,7 +2,6 @@ package org.springframework.security.config.http;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
@@ -11,6 +10,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
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.intercept.DefaultFilterInvocationSecurityMetadataSource;
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.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.util.AnyRequestMatcher;
public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator {
private Log logger = LogFactory.getLog(getClass());
public void validate(FilterChainProxy fcp) {
Map<String, List<Filter>> filterChainMap = fcp.getFilterChainMap();
for(String pattern : fcp.getFilterChainMap().keySet()) {
List<Filter> filters = filterChainMap.get(pattern);
for(List<Filter> filters : fcp.getFilterChainMap().values()) {
checkFilterStack(filters);
}
checkLoginPageIsntProtected(fcp, filterChainMap.get(fcp.getMatcher().getUniversalMatchPattern()));
checkLoginPageIsntProtected(fcp);
}
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 */
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);
if (etf.getAuthenticationEntryPoint() instanceof LoginUrlAuthenticationEntryPoint) {
String loginPage =
((LoginUrlAuthenticationEntryPoint)etf.getAuthenticationEntryPoint()).getLoginFormUrl();
FilterInvocation loginRequest = new FilterInvocation(loginPage, "POST");
List<Filter> filters = fcp.getFilters(loginPage);
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);
DefaultFilterInvocationSecurityMetadataSource fids =
(DefaultFilterInvocationSecurityMetadataSource) fsi.getSecurityMetadataSource();
Collection<ConfigAttribute> attributes = fids.lookupAttributes(loginPage, "POST");
Collection<ConfigAttribute> attributes = fids.getAttributes(loginRequest);
if (attributes == null) {
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(),
anonPF.getUserAttribute().getAuthorities());
try {
fsi.getAccessDecisionManager().decide(token, new Object(), fids.lookupAttributes(loginPage, "POST"));
fsi.getAccessDecisionManager().decide(token, new Object(), attributes);
} catch (Exception e) {
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 " +
@@ -1,5 +1,10 @@
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.BeanDefinitionHolder;
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.ParserContext;
import org.springframework.security.config.Elements;
import org.springframework.security.web.util.RegexUrlPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.*;
/**
* Sets the filter chain Map for a FilterChainProxy bean declaration.
*
@@ -30,11 +32,7 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
Map filterChainMap = new LinkedHashMap();
Element elt = (Element)node;
String pathType = elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_PATH_TYPE);
if (HttpSecurityBeanDefinitionParser.OPT_PATH_TYPE_REGEX.equals(pathType)) {
filterChainProxy.getPropertyValues().addPropertyValue("matcher", new RegexUrlPathMatcher());
}
MatcherType matcherType = MatcherType.fromElement(elt);
List<Element> filterChainElts = DomUtils.getChildElementsByTagName(elt, Elements.FILTER_CHAIN);
@@ -52,8 +50,10 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
"'must not be empty", elt);
}
BeanDefinition matcher = matcherType.createMatcher(path, null);
if (filters.equals(HttpSecurityBeanDefinitionParser.OPT_FILTERS_NONE)) {
filterChainMap.put(path, Collections.EMPTY_LIST);
filterChainMap.put(matcher, Collections.EMPTY_LIST);
} else {
String[] filterBeanNames = StringUtils.tokenizeToStringArray(filters, ",");
ManagedList filterChain = new ManagedList(filterBeanNames.length);
@@ -62,7 +62,7 @@ public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDeco
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.intercept.DefaultFilterInvocationSecurityMetadataSource;
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.xml.DomUtils;
import org.w3c.dom.Element;
@@ -63,11 +60,11 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
}
static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(elt);
MatcherType matcherType = MatcherType.fromElement(elt);
boolean useExpressions = isUseExpressions(elt);
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
interceptUrls, useExpressions, pc);
matcherType, interceptUrls, useExpressions, pc);
BeanDefinitionBuilder fidsBuilder;
if (useExpressions) {
@@ -83,16 +80,14 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
}
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
} else {
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
}
fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
// fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));
return fidsBuilder.getBeanDefinition();
@@ -102,8 +97,9 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
return "true".equals(elt.getAttribute(ATT_USE_EXPRESSIONS));
}
private static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts,
boolean useExpressions, ParserContext parserContext) {
private static ManagedMap<BeanDefinition, BeanDefinition>
parseInterceptUrlsForFilterInvocationRequestMap(MatcherType matcherType,
List<Element> urlElts, boolean useExpressions, ParserContext parserContext) {
ManagedMap<BeanDefinition, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanDefinition, BeanDefinition>();
@@ -124,10 +120,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
method = null;
}
BeanDefinitionBuilder keyBldr = BeanDefinitionBuilder.rootBeanDefinition(RequestKey.class);
keyBldr.addConstructorArgValue(path);
keyBldr.addConstructorArgValue(method);
BeanDefinition matcher = matcherType.createMatcher(path, method);
BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder.rootBeanDefinition(SecurityConfig.class);
attributeBuilder.addConstructorArgValue(access);
@@ -140,13 +133,11 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
attributeBuilder.setFactoryMethod("createListFromCommaDelimitedString");
}
BeanDefinition key = keyBldr.getBeanDefinition();
if (filterInvocationDefinitionMap.containsKey(key)) {
if (filterInvocationDefinitionMap.containsKey(matcher)) {
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;
@@ -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.intercept.DefaultFilterInvocationSecurityMetadataSource;
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.session.ConcurrentSessionControlStrategy;
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.session.ConcurrentSessionFilter;
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.xml.DomUtils;
import org.w3c.dom.Element;
@@ -81,10 +78,9 @@ class HttpConfigurationBuilder {
private final Element httpElt;
private final ParserContext pc;
private final UrlMatcher matcher;
private final Boolean convertPathsToLowerCase;
private final SessionCreationPolicy sessionPolicy;
private final List<Element> interceptUrls;
private final MatcherType matcherType;
// Use ManagedMap to allow placeholder resolution
private ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap;
@@ -102,15 +98,12 @@ class HttpConfigurationBuilder {
private BeanReference fsi;
private BeanReference requestCache;
public HttpConfigurationBuilder(Element element, ParserContext pc, UrlMatcher matcher,
public HttpConfigurationBuilder(Element element, ParserContext pc, MatcherType matcherType,
String portMapperName, BeanReference authenticationManager) {
this.httpElt = element;
this.pc = pc;
this.portMapperName = portMapperName;
this.matcher = matcher;
// 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();
this.matcherType = matcherType;
interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
String createSession = element.getAttribute(ATT_CREATE_SESSION);
@@ -139,10 +132,7 @@ class HttpConfigurationBuilder {
pc.getReaderContext().error("path attribute cannot be empty or null", urlElt);
}
BeanDefinitionBuilder pathBean = BeanDefinitionBuilder.rootBeanDefinition(HttpConfigurationBuilder.class);
pathBean.setFactoryMethod("createPath");
pathBean.addConstructorArgValue(path);
pathBean.addConstructorArgValue(convertPathsToLowerCase);
BeanDefinition matcher = matcherType.createMatcher(path, null);
String filters = urlElt.getAttribute(ATT_FILTERS);
@@ -153,7 +143,7 @@ class HttpConfigurationBuilder {
}
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);
BeanDefinitionBuilder metadataSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
metadataSourceBldr.addConstructorArgValue(matcher);
metadataSourceBldr.addConstructorArgValue(channelRequestMap);
metadataSourceBldr.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
// metadataSourceBldr.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
channelFilter.getPropertyValues().addPropertyValue("securityMetadataSource", metadataSourceBldr.getBeanDefinition());
RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class);
@@ -413,26 +402,22 @@ class HttpConfigurationBuilder {
for (Element urlElt : interceptUrls) {
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
String method = urlElt.getAttribute(ATT_HTTP_METHOD);
if(!StringUtils.hasText(path)) {
pc.getReaderContext().error("path attribute cannot be empty or null", urlElt);
}
if (convertPathsToLowerCase) {
path = path.toLowerCase();
pc.getReaderContext().error("pattern attribute cannot be empty or null", urlElt);
}
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
if (StringUtils.hasText(requiredChannel)) {
BeanDefinition requestKey = new RootBeanDefinition(RequestKey.class);
requestKey.getConstructorArgumentValues().addGenericArgumentValue(path);
BeanDefinition matcher = matcherType.createMatcher(path, method);
RootBeanDefinition channelAttributes = new RootBeanDefinition(ChannelAttributeFactory.class);
channelAttributes.getConstructorArgumentValues().addGenericArgumentValue(requiredChannel);
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.authentication.AuthenticationManagerFactoryBean;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.RegexUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher;
import org.springframework.security.web.util.AnyRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -44,17 +42,13 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
private static final Log logger = LogFactory.getLog(HttpSecurityBeanDefinitionParser.class);
static final String ATT_PATH_PATTERN = "pattern";
static final String ATT_PATH_TYPE = "path-type";
static final String OPT_PATH_TYPE_REGEX = "regex";
private static final String DEF_PATH_TYPE_ANT = "ant";
static final String ATT_HTTP_METHOD = "method";
static final String ATT_FILTERS = "filters";
static final String OPT_FILTERS_NONE = "none";
static final String ATT_REQUIRES_CHANNEL = "requires-channel";
private static final String ATT_LOWERCASE_COMPARISONS = "lowercase-comparisons";
private static final String ATT_REF = "ref";
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 String portMapperName = createPortMapper(element, pc);
final UrlMatcher matcher = createUrlMatcher(element);
MatcherType matcherType = MatcherType.fromElement(element);
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcher,
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcherType,
portMapperName, authenticationManager);
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
httpBldr.getSessionStrategy());
authenticationProviders.addAll(authBldr.getProviders());
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
unorderedFilterChain.addAll(httpBldr.getFilters());
unorderedFilterChain.addAll(authBldr.getFilters());
authenticationProviders.addAll(authBldr.getProviders());
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
Collections.sort(unorderedFilterChain, new OrderComparator());
@@ -111,11 +105,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
ManagedMap<BeanDefinition, List<BeanMetadataElement>> filterChainMap = httpBldr.getFilterChainMap();
BeanDefinition universalMatch = new RootBeanDefinition(String.class);
universalMatch.getConstructorArgumentValues().addGenericArgumentValue(matcher.getUniversalMatchPattern());
BeanDefinition universalMatch = new RootBeanDefinition(AnyRequestMatcher.class);
filterChainMap.put(universalMatch, filterChain);
registerFilterChainProxy(pc, filterChainMap, matcher, source);
registerFilterChainProxy(pc, filterChainMap, source);
pc.popAndRegisterContainingComponent();
return null;
@@ -222,57 +215,20 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
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)) {
pc.getReaderContext().error("Duplicate <http> element detected", source);
}
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
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);
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
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 {
@@ -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 =
## Whether a string should be base64 encoded
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 =
## 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"}
@@ -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.
attribute security-context-repository-ref {xsd:token}?
http.attlist &=
## The path format used to define the paths in child elements.
path-type?
request-matcher?
http.attlist &=
## Whether test URLs should be converted to lower case prior to comparing with defined path patterns. If unspecified, defaults to "true".
attribute lowercase-comparisons {boolean}?
## Deprecated. Use request-matcher instead.
path-type?
http.attlist &=
## 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}?
@@ -392,7 +394,10 @@ filter-chain-map =
## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
element filter-chain-map {filter-chain-map.attlist, filter-chain+}
filter-chain-map.attlist &=
path-type
## Deprecated. Use request-matcher instead.
path-type?
filter-chain-map.attlist &=
request-matcher?
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.
@@ -413,8 +418,10 @@ fsmds.attlist &=
## as for http element
attribute lowercase-comparisons {boolean}?
fsmds.attlist &=
## as for http element
## Deprecate. Use request-matcher instead.
path-type?
fsmds.attlist &=
request-matcher?
filter-invocation-definition-source =
## Deprecated synonym for filter-security-metadata-source
@@ -31,6 +31,20 @@
</xs:simpleType>
</xs:attribute>
</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:attribute name="path-type" use="required">
<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:annotation>
</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: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>
@@ -703,11 +729,6 @@
</xs:restriction>
</xs:simpleType>
</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: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>
@@ -902,7 +923,29 @@
<xs:attributeGroup ref="security:filter-chain-map.attlist"/>
</xs:complexType></xs:element>
<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 name="filter-chain.attlist">
@@ -948,6 +991,18 @@
</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:element name="filter-invocation-definition-source"><xs:annotation>
<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.context.SecurityContextPersistenceFilter;
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}.
@@ -101,27 +104,15 @@ public class FilterChainProxyConfigTests {
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
@Test
public void mixingPatternsAndPlaceholdersDoesntCauseOrderingIssues() throws Exception {
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class);
String[] paths = filterChainProxy.getFilterChainMap().keySet().toArray(new String[0]);
assertEquals("/login*", paths[0]);
assertEquals("/logout", paths[1]);
assertEquals("/**", paths[2]);
RequestMatcher[] matchers = filterChainProxy.getFilterChainMap().keySet().toArray(new RequestMatcher[0]);
assertEquals("/login*", ((AntPathRequestMatcher)matchers[0]).getPattern());
assertEquals("/logout", ((AntPathRequestMatcher)matchers[1]).getPattern());
assertTrue(matchers[2] instanceof AnyRequestMatcher);
}
private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {
@@ -125,9 +125,6 @@ public class HttpSecurityBeanDefinitionParserTests {
List<Filter> filterList = getFilters("/anyurl");
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)
@@ -136,8 +133,6 @@ public class HttpSecurityBeanDefinitionParserTests {
}
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();
assertTrue(filters.next() instanceof SecurityContextPersistenceFilter);
@@ -184,37 +179,34 @@ public class HttpSecurityBeanDefinitionParserTests {
assertTrue(filters.size() == 0);
}
@Test
public void regexPathsWorkCorrectly() throws Exception {
setContext(
" <http auto-config='true' path-type='regex'>" +
" <http auto-config='true' request-matcher='regex'>" +
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
" </http>" + AUTH_PROVIDER_XML);
assertEquals(0, getFilters("/imlowercase").size());
// This will be matched by the default pattern ".*"
List<Filter> allFilters = getFilters("/ImCaughtByTheUniversalMatchPattern");
List<Filter> allFilters = getFilters("/ImCaughtByTheAnyRequestMatcher");
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
public void lowerCaseComparisonAttributeIsRespectedByFilterChainProxy() throws Exception {
public void ciRegexPathsWorkCorrectly() throws Exception {
setContext(
" <http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
" <intercept-url pattern='/Secure*' filters='none' />" +
" <http auto-config='true' request-matcher='ciRegex'>" +
" <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
" </http>" + AUTH_PROVIDER_XML);
assertEquals(0, getFilters("/Secure").size());
// These will be matched by the default pattern "/**"
checkAutoConfigFilters(getFilters("/secure"));
checkAutoConfigFilters(getFilters("/ImCaughtByTheUniversalMatchPattern"));
assertEquals(0, getFilters("/imMixedCase").size());
// This will be matched by the default pattern ".*"
List<Filter> allFilters = getFilters("/Im_Caught_By_The_AnyRequestMatcher");
assertTrue(allFilters.size() > 0);
checkAutoConfigFilters(allFilters);
}
@Test
public void formLoginWithNoLoginPageAddsDefaultLoginPageFilter() throws Exception {
setContext(
"<http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
"<http auto-config='true' request-matcher='ant'>" +
" <form-login />" +
"</http>" + AUTH_PROVIDER_XML);
// These will be matched by the default pattern "/**"
@@ -315,26 +307,6 @@ public class HttpSecurityBeanDefinitionParserTests {
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
@Test
public void interceptUrlsAndFormLoginSupportPropertyPlaceholders() throws Exception {
@@ -395,9 +367,9 @@ public class HttpSecurityBeanDefinitionParserTests {
public void httpMethodMatchIsSupported() throws Exception {
setContext(
" <http auto-config='true'>" +
" <intercept-url pattern='/**' access='ROLE_C' />" +
" <intercept-url pattern='/secure*' method='DELETE' access='ROLE_SUPERVISOR' />" +
" <intercept-url pattern='/secure*' method='POST' access='ROLE_A,ROLE_B' />" +
" <intercept-url pattern='/**' access='ROLE_C' />" +
" </http>" + AUTH_PROVIDER_XML);
FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);
@@ -692,7 +664,6 @@ public class HttpSecurityBeanDefinitionParserTests {
"</http>" +
"<b:bean id='userService' class='org.springframework.security.core.userdetails.MockUserDetailsService'/> " +
AUTH_PROVIDER_XML);
// AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
}
@Test
@@ -746,7 +717,6 @@ public class HttpSecurityBeanDefinitionParserTests {
"</http>" +
"<b:bean id='ss' class='org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy'/>"
+ AUTH_PROVIDER_XML);
//session-authentication-strategy-ref
}
@Test
@@ -768,12 +738,10 @@ public class HttpSecurityBeanDefinitionParserTests {
getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
// Object sessionRegistryFromController = FieldUtils.getFieldValue(getConcurrentSessionController(),"sessionRegistry");
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry");
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
// assertSame(sessionRegistry, sessionRegistryFromController);
assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
// SEC-1143
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
@@ -791,8 +759,6 @@ public class HttpSecurityBeanDefinitionParserTests {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
SecurityContextHolder.getContext().setAuthentication(auth);
// Register 2 sessions and then check a third
// req.setSession(new MockHttpSession());
// auth.setDetails(new WebAuthenticationDetails(req));
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
SaveContextOnUpdateOrErrorResponseWrapper response = new SaveContextOnUpdateOrErrorResponseWrapper(mockResponse, false) {
protected void saveContext(SecurityContext context) {
@@ -1240,7 +1206,6 @@ public class HttpSecurityBeanDefinitionParserTests {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod(method);
request.setRequestURI(null);
request.setServletPath(path);
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 id="newFilterChainProxyNonNamespace" class="org.springframework.security.web.FilterChainProxy">
<property name="matcher">
<bean class="org.springframework.security.web.util.AntUrlPathMatcher"/>
</property>
<property name="filterChainMap">
<map>
<entry key="/foo/**">
<entry>
<key>
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
<constructor-arg value="/foo/**"/>
</bean>
</key>
<list>
<ref local="mockFilter"/>
</list>
</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>
<ref local="sif"/>
<ref local="mockFilter"/>
<ref local="mockFilter2"/>
</list>
</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/>
</entry>
<entry key="/**">
<entry>
<key>
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
<constructor-arg value="/**"/>
</bean>
</key>
<list>
<ref local="sif"/>
<ref local="apf"/>