mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-02-28 18:39:06 +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.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"/>
|
||||
|
@ -26,16 +26,15 @@ import java.util.Set;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
||||
import org.springframework.security.web.util.UrlMatcher;
|
||||
import org.springframework.security.web.util.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
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.
|
||||
* 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
|
||||
* by the default <tt><security:http /></tt> namespace configuration options.
|
||||
*
|
||||
* <p>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()}
|
||||
* and {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} invocations through to each <code>Filter</code>
|
||||
* defined against <code>FilterChainProxy</code>.
|
||||
*
|
||||
* <p>As of version 2.0, <tt>FilterChainProxy</tt> is configured using an ordered Map of path patterns to <tt>List</tt>s
|
||||
* of <tt>Filter</tt> objects. In previous
|
||||
* 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.
|
||||
* by the default <tt><security:http /></tt> namespace configuration options.
|
||||
* <p>
|
||||
* The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
|
||||
* <code>web.xml</code>.
|
||||
* <p>
|
||||
* 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
|
||||
* 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
|
||||
* of filters (as comma-separated bean names) which should be applied to requests which match the pattern.
|
||||
* An example configuration might look like this:
|
||||
*
|
||||
* <pre>
|
||||
<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="/**" filters="filter1,filter2,filter3"/>
|
||||
</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
|
||||
* 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
|
||||
* Please consult the security namespace schema file for a full list of available configuration options.
|
||||
*
|
||||
* use of the value "none" for the "filters" can be used to exclude a request pattern from the security filter chain
|
||||
* entirely. Please consult the security namespace schema file for a full list of available configuration options.
|
||||
* <p>
|
||||
* Each possible URI 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
|
||||
* <code>Filter</code>s that apply to that request. NB: This means you must put most specific URI patterns at the top
|
||||
* of the list, and ensure all <code>Filter</code>s that should apply for a given URI pattern are entered against the
|
||||
* respective entry. The <code>FilterChainProxy</code> will not iterate the remainder of the URI patterns to locate
|
||||
* additional <code>Filter</code>s.
|
||||
*
|
||||
* <p><code>FilterChainProxy</code> respects normal handling of <code>Filter</code>s that elect not to call {@link
|
||||
* Each possible pattern that <code>FilterChainProxy</code> should service must be entered.
|
||||
* The first match for a given request will be used to define all of the <code>Filter</code>s that apply to that
|
||||
* request. This means you must put most specific matches at the top of the list, and ensure all <code>Filter</code>s
|
||||
* that should apply for a given matcher are entered against the respective entry.
|
||||
* The <code>FilterChainProxy</code> will not iterate through the remainder of the map entries to locate additional
|
||||
* <code>Filter</code>s.
|
||||
* <p>
|
||||
* <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.FilterChain)}, in that the remainder of the original or <code>FilterChainProxy</code>-declared filter
|
||||
* chain will not be called.
|
||||
*
|
||||
* <p>Note the <code>Filter</code> lifecycle mismatch between the servlet container and IoC
|
||||
* <p>
|
||||
* 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 to manage the lifecycle instead of the servlet container. By default the <code>DelegatingFilterProxy</code>
|
||||
* 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.
|
||||
* container to manage the lifecycle instead of the servlet container.
|
||||
*
|
||||
* @author Carlos Sanchez
|
||||
* @author Ben Alex
|
||||
@ -111,20 +100,15 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
// private ApplicationContext applicationContext;
|
||||
/** 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 Map<RequestMatcher, List<Filter>> filterChainMap;
|
||||
|
||||
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notNull(uncompiledFilterChainMap, "filterChainMap must be set");
|
||||
Assert.notNull(filterChainMap, "filterChainMap must be set");
|
||||
filterChainValidator.validate(this);
|
||||
}
|
||||
|
||||
@ -132,7 +116,7 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
throws IOException, ServletException {
|
||||
|
||||
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 (logger.isDebugEnabled()) {
|
||||
@ -149,40 +133,18 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public List<Filter> getFilters(String url) {
|
||||
if (stripQueryStringFromUrls) {
|
||||
// String query string - see SEC-953
|
||||
int firstQuestionMarkIndex = url.indexOf("?");
|
||||
private List<Filter> getFilters(HttpServletRequest request) {
|
||||
for (Map.Entry<RequestMatcher, List<Filter>> entry : filterChainMap.entrySet()) {
|
||||
RequestMatcher matcher = entry.getKey();
|
||||
|
||||
if (firstQuestionMarkIndex != -1) {
|
||||
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) {
|
||||
if (matcher.matches(request)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
@ -190,6 +152,16 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
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
|
||||
* filter chains.
|
||||
@ -225,15 +197,14 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setFilterChainMap(Map filterChainMap) {
|
||||
checkContents(filterChainMap);
|
||||
uncompiledFilterChainMap = new LinkedHashMap<String, List<Filter>>(filterChainMap);
|
||||
this.filterChainMap = new LinkedHashMap<RequestMatcher, List<Filter>>(filterChainMap);
|
||||
checkPathOrder();
|
||||
createCompiledMap();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void checkContents(Map filterChainMap) {
|
||||
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);
|
||||
Assert.isInstanceOf(List.class, filters, "Value must be a filter list");
|
||||
// Check the contents
|
||||
@ -248,50 +219,25 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
|
||||
private void checkPathOrder() {
|
||||
// Check that the universal pattern is listed at the end, if at all
|
||||
String[] paths = (String[]) uncompiledFilterChainMap.keySet().toArray(new String[0]);
|
||||
String universalMatch = matcher.getUniversalMatchPattern();
|
||||
RequestMatcher[] matchers = filterChainMap.keySet().toArray(new RequestMatcher[0]);
|
||||
|
||||
for (int i=0; i < paths.length-1; i++) {
|
||||
if (paths[i].equals(universalMatch)) {
|
||||
throw new IllegalArgumentException("A universal match pattern " + universalMatch + " is defined " +
|
||||
for (int i=0; i < matchers.length-1; i++) {
|
||||
if (matchers[i] instanceof AnyRequestMatcher) {
|
||||
throw new IllegalArgumentException("A universal match pattern ('/**') is defined " +
|
||||
" 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* 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).
|
||||
*/
|
||||
public Map<String, List<Filter>> getFilterChainMap() {
|
||||
return new LinkedHashMap<String, List<Filter>>(uncompiledFilterChainMap);
|
||||
}
|
||||
|
||||
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;
|
||||
public Map<RequestMatcher, List<Filter>> getFilterChainMap() {
|
||||
return new LinkedHashMap<RequestMatcher, List<Filter>>(filterChainMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -306,9 +252,8 @@ public class FilterChainProxy extends GenericFilterBean {
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("FilterChainProxy[");
|
||||
sb.append(" UrlMatcher = ").append(matcher);
|
||||
sb.append("; Filter Chains: ");
|
||||
sb.append(uncompiledFilterChainMap);
|
||||
sb.append("Filter Chains: ");
|
||||
sb.append(filterChainMap);
|
||||
sb.append("]");
|
||||
|
||||
return sb.toString();
|
||||
|
@ -15,13 +15,28 @@
|
||||
|
||||
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.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.springframework.security.web.util.UrlUtils;
|
||||
|
||||
|
||||
/**
|
||||
@ -36,6 +51,15 @@ import javax.servlet.http.HttpServletResponse;
|
||||
* @author colin sampaleanu
|
||||
*/
|
||||
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 ================================================================================================
|
||||
|
||||
private FilterChain chain;
|
||||
@ -54,6 +78,26 @@ public class FilterInvocation {
|
||||
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 ========================================================================================================
|
||||
|
||||
public FilterChain getChain() {
|
||||
@ -101,3 +145,386 @@ public class FilterInvocation {
|
||||
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;
|
||||
|
||||
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.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.LogFactory;
|
||||
@ -59,14 +39,6 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv
|
||||
|
||||
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 ================================================================================================
|
||||
|
||||
private AbstractSecurityInterceptor securityInterceptor;
|
||||
@ -114,11 +86,7 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv
|
||||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
||||
Assert.notNull(uri, "uri parameter is required");
|
||||
|
||||
if (contextPath == null) {
|
||||
contextPath = "/ctxpath";
|
||||
}
|
||||
|
||||
FilterInvocation fi = createFilterInvocation(contextPath, uri, method);
|
||||
FilterInvocation fi = new FilterInvocation(contextPath, uri, method);
|
||||
Collection<ConfigAttribute> attrs = securityInterceptor.obtainSecurityMetadataSource().getAttributes(fi);
|
||||
|
||||
if (attrs == null) {
|
||||
@ -145,389 +113,6 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv
|
||||
|
||||
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.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.access.intercept.RequestKey;
|
||||
import org.springframework.security.web.util.UrlMatcher;
|
||||
import org.springframework.security.web.util.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -24,21 +23,22 @@ import org.springframework.util.Assert;
|
||||
public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
|
||||
private final static Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
|
||||
|
||||
public ExpressionBasedFilterInvocationSecurityMetadataSource(UrlMatcher urlMatcher,
|
||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestMap, WebSecurityExpressionHandler expressionHandler) {
|
||||
super(urlMatcher, processMap(requestMap, expressionHandler.getExpressionParser()));
|
||||
public ExpressionBasedFilterInvocationSecurityMetadataSource(
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap,
|
||||
WebSecurityExpressionHandler expressionHandler) {
|
||||
super(processMap(requestMap, expressionHandler.getExpressionParser()));
|
||||
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
|
||||
}
|
||||
|
||||
private static LinkedHashMap<RequestKey, Collection<ConfigAttribute>> processMap(
|
||||
LinkedHashMap<RequestKey,Collection<ConfigAttribute>> requestMap, ExpressionParser parser) {
|
||||
private static LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processMap(
|
||||
LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>> requestMap, ExpressionParser parser) {
|
||||
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
|
||||
|
||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestToExpressionAttributesMap =
|
||||
new LinkedHashMap<RequestKey, Collection<ConfigAttribute>>(requestMap);
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestToExpressionAttributesMap =
|
||||
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>(requestMap);
|
||||
|
||||
for (Map.Entry<RequestKey, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||
RequestKey request = entry.getKey();
|
||||
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||
RequestMatcher request = entry.getKey();
|
||||
Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request);
|
||||
ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
|
||||
String expression = entry.getValue().toArray(new ConfigAttribute[1])[0].getAttribute();
|
||||
|
@ -15,192 +15,75 @@
|
||||
|
||||
package org.springframework.security.web.access.intercept;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
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>.
|
||||
* <p>
|
||||
* Stores an ordered map of compiled URL paths to <tt>ConfigAttribute</tt> lists and provides URL matching
|
||||
* against the items stored in this map using the configured <tt>UrlMatcher</tt>.
|
||||
* Stores an ordered map of {@link RequestMatcher}s to <tt>ConfigAttribute</tt> collections and provides matching
|
||||
* of {@code FilterInvocation}s against the items stored in the map.
|
||||
* <p>
|
||||
* The order of the URL paths in the map is very important.
|
||||
* The system will identify the <b>first</b> matching path for a given HTTP URL. It will not proceed to evaluate
|
||||
* later paths if a match has already been found. Accordingly, the most specific matches should be
|
||||
* registered first, with the most general matches registered last.
|
||||
* The order of the {@link RequestMatcher}s in the map is very important. The <b>first</b> one which matches the
|
||||
* request will be used. Later matchers in the map will not be invoked if a match has already been found.
|
||||
* Accordingly, the most specific matchers should be registered first, with the most general matches registered last.
|
||||
* <p>
|
||||
* If URL paths are registered for a particular HTTP method using, then the method-specific matches will take
|
||||
* precedence over any URLs which are registered without an HTTP method.
|
||||
* The most common method creating an instance is using the Spring Security namespace. For example, the {@code pattern}
|
||||
* 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 Luke Taylor
|
||||
*/
|
||||
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());
|
||||
|
||||
//private Map<Object, List<ConfigAttribute>> requestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
|
||||
/** 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;
|
||||
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
/**
|
||||
* Builds the internal request map from the supplied map. The key elements should be of type {@link RequestKey},
|
||||
* which contains a URL path and an optional HTTP method (may be null). The path stored in the key will depend on
|
||||
* Sets the internal request map from the supplied map. The key elements should be of type {@link RequestMatcher},
|
||||
* which. The path stored in the key will depend on
|
||||
* 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
|
||||
*/
|
||||
public DefaultFilterInvocationSecurityMetadataSource(UrlMatcher urlMatcher,
|
||||
LinkedHashMap<RequestKey, Collection<ConfigAttribute>> requestMap) {
|
||||
this.urlMatcher = urlMatcher;
|
||||
public DefaultFilterInvocationSecurityMetadataSource(
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
|
||||
|
||||
for (Map.Entry<RequestKey, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||
addSecureUrl(entry.getKey().getUrl(), entry.getKey().getMethod(), entry.getValue());
|
||||
}
|
||||
this.requestMap = requestMap;
|
||||
}
|
||||
|
||||
//~ 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() {
|
||||
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
|
||||
|
||||
for (Map.Entry<String, Map<Object, Collection<ConfigAttribute>>> entry : httpMethodMap.entrySet()) {
|
||||
for (Collection<ConfigAttribute> attrs : entry.getValue().values()) {
|
||||
allAttributes.addAll(attrs);
|
||||
}
|
||||
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||
allAttributes.addAll(entry.getValue());
|
||||
}
|
||||
|
||||
return allAttributes;
|
||||
}
|
||||
|
||||
|
||||
public Collection<ConfigAttribute> getAttributes(Object object) {
|
||||
if ((object == null) || !this.supports(object.getClass())) {
|
||||
throw new IllegalArgumentException("Object must be a FilterInvocation");
|
||||
}
|
||||
|
||||
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) {
|
||||
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
|
||||
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
|
||||
if (entry.getKey().matches(request)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
@ -210,16 +93,4 @@ public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvo
|
||||
public boolean supports(Class<?> 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.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.access.intercept;
|
||||
package org.springframework.security.web;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
|
||||
/**
|
||||
* Tests {@link FilterInvocation}.
|
||||
@ -115,4 +118,18 @@ public class FilterInvocationTests {
|
||||
assertEquals("FilterInvocation: URL: /HelloWorld", fi.toString());
|
||||
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.Mockito.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
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));
|
||||
}
|
||||
|
||||
@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.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.access.intercept.RequestKey;
|
||||
import org.springframework.security.web.util.AntUrlPathMatcher;
|
||||
import org.springframework.security.web.util.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Tests parts of {@link DefaultFilterInvocationSecurityMetadataSource} not tested by {@link
|
||||
@ -39,41 +38,25 @@ import org.springframework.security.web.util.AntUrlPathMatcher;
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
private DefaultFilterInvocationSecurityMetadataSource fids;
|
||||
private Collection<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
private void createFids(String url, String method) {
|
||||
LinkedHashMap requestMap = new LinkedHashMap();
|
||||
requestMap.put(new RequestKey(url, method), def);
|
||||
fids = new DefaultFilterInvocationSecurityMetadataSource(new AntUrlPathMatcher(), requestMap);
|
||||
fids.setStripQueryStringFromUrls(true);
|
||||
}
|
||||
|
||||
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());
|
||||
private void createFids(String pattern, String method) {
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap =
|
||||
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
|
||||
requestMap.put(new AntPathRequestMatcher(pattern, method), def);
|
||||
fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -113,9 +86,9 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -128,7 +101,7 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
public void httpMethodLookupSucceeds() {
|
||||
createFids("/somepage**", "GET");
|
||||
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", "GET");
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
|
||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||
assertEquals(def, attrs);
|
||||
}
|
||||
@ -137,7 +110,7 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
|
||||
createFids("/somepage**", null);
|
||||
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", "GET");
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
|
||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||
assertEquals(def, attrs);
|
||||
}
|
||||
@ -146,50 +119,23 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
public void requestWithDifferentHttpMethodDoesntMatch() {
|
||||
createFids("/somepage**", "GET");
|
||||
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null);
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST");
|
||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||
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
|
||||
@Test
|
||||
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");
|
||||
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");
|
||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||
assertEquals(userAttrs, attrs);
|
||||
}
|
||||
requestMap.put(new AntPathRequestMatcher("/user/**", null), userAttrs);
|
||||
requestMap.put(new AntPathRequestMatcher("/teller/**", "GET"), SecurityConfig.createList("B"));
|
||||
fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
|
||||
|
||||
@Test
|
||||
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");
|
||||
FilterInvocation fi = createFilterInvocation("/user", null, null, "GET");
|
||||
Collection<ConfigAttribute> attrs = fids.getAttributes(fi);
|
||||
assertEquals(userAttrs, attrs);
|
||||
}
|
||||
@ -201,23 +147,24 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
public void extraQuestionMarkStillMatches() {
|
||||
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);
|
||||
|
||||
fi = createFilterInvocation("/someAdminPage.html??", null);
|
||||
fi = createFilterInvocation("/someAdminPage.html", null, "?", null);
|
||||
|
||||
response = fids.lookupAttributes(fi.getRequestUrl(), null);
|
||||
response = fids.getAttributes(fi);
|
||||
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();
|
||||
request.setRequestURI(null);
|
||||
request.setMethod(method);
|
||||
|
||||
request.setServletPath(path);
|
||||
request.setServletPath(servletPath);
|
||||
request.setPathInfo(pathInfo);
|
||||
request.setQueryString(queryString);
|
||||
|
||||
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.expression;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.mock.web;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