mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-30 07:42:52 +00:00
SEC-640: Add namespace support for FilterInvocationDefinitionSource configuration
http://jira.springframework.org/browse/SEC-640
This commit is contained in:
parent
1bc863fce1
commit
33023565a8
@ -34,4 +34,5 @@ abstract class Elements {
|
|||||||
public static final String CUSTOM_FILTER = "custom-filter";
|
public static final String CUSTOM_FILTER = "custom-filter";
|
||||||
public static final String CUSTOM_AUTH_RPOVIDER = "custom-authentication-provider";
|
public static final String CUSTOM_AUTH_RPOVIDER = "custom-authentication-provider";
|
||||||
public static final String X509 = "x509";
|
public static final String X509 = "x509";
|
||||||
|
public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.springframework.security.config;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||||
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
|
import org.springframework.security.util.AntUrlPathMatcher;
|
||||||
|
import org.springframework.security.util.UrlMatcher;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.util.xml.DomUtils;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows for convenient creation of a {@link FilterInvocationDefinitionSource} bean for use with a FilterSecurityInterceptor.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class FilterInvocationDefinitionSourceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
|
||||||
|
|
||||||
|
protected String getBeanClassName(Element element) {
|
||||||
|
return "org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||||
|
List interceptUrls = DomUtils.getChildElementsByTagName(element, "intercept-url");
|
||||||
|
|
||||||
|
// Check for attributes that aren't allowed in this context
|
||||||
|
Iterator interceptUrlElts = interceptUrls.iterator();
|
||||||
|
while(interceptUrlElts.hasNext()) {
|
||||||
|
Element elt = (Element) interceptUrlElts.next();
|
||||||
|
if (StringUtils.hasLength(elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL))) {
|
||||||
|
parserContext.getReaderContext().error("The attribute '" + HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL + "' isn't allowed here.", elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_FILTERS))) {
|
||||||
|
parserContext.getReaderContext().error("The attribute '" + HttpSecurityBeanDefinitionParser.ATT_FILTERS + "' isn't allowed here.", elt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(element);
|
||||||
|
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
|
||||||
|
|
||||||
|
LinkedHashMap requestMap = new LinkedHashMap();
|
||||||
|
HttpSecurityBeanDefinitionParser.parseInterceptUrlsForFilterInvocationRequestMap(interceptUrls, requestMap,
|
||||||
|
convertPathsToLowerCase, parserContext);
|
||||||
|
|
||||||
|
builder.addConstructorArg(matcher);
|
||||||
|
builder.addConstructorArg(requestMap);
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFil
|
|||||||
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
||||||
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
|
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
|
||||||
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
||||||
|
import org.springframework.security.intercept.web.RequestKey;
|
||||||
import org.springframework.security.securechannel.ChannelDecisionManagerImpl;
|
import org.springframework.security.securechannel.ChannelDecisionManagerImpl;
|
||||||
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
||||||
import org.springframework.security.securechannel.InsecureChannelProcessor;
|
import org.springframework.security.securechannel.InsecureChannelProcessor;
|
||||||
@ -111,45 +112,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
= BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
|
= BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
|
||||||
|
|
||||||
Map filterChainMap = new LinkedHashMap();
|
Map filterChainMap = new LinkedHashMap();
|
||||||
|
|
||||||
String patternType = element.getAttribute(ATT_PATH_TYPE);
|
UrlMatcher matcher = createUrlMatcher(element);
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultFilterInvocationDefinitionSource interceptorFilterInvDefSource =
|
|
||||||
new DefaultFilterInvocationDefinitionSource(matcher);
|
|
||||||
DefaultFilterInvocationDefinitionSource channelFilterInvDefSource =
|
|
||||||
new DefaultFilterInvocationDefinitionSource(matcher);
|
|
||||||
|
|
||||||
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
|
filterChainProxy.getPropertyValues().addPropertyValue("matcher", matcher);
|
||||||
|
|
||||||
// Add servlet-api integration filter if required
|
// Add servlet-api integration filter if required
|
||||||
@ -164,8 +129,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
|
|
||||||
filterChainProxy.getPropertyValues().addPropertyValue("filterChainMap", filterChainMap);
|
filterChainProxy.getPropertyValues().addPropertyValue("filterChainMap", filterChainMap);
|
||||||
|
|
||||||
filterSecurityInterceptorBuilder.addPropertyValue("objectDefinitionSource", interceptorFilterInvDefSource);
|
|
||||||
|
|
||||||
// Set up the access manager and authentication mananger references for http
|
// Set up the access manager and authentication mananger references for http
|
||||||
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
|
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
|
||||||
|
|
||||||
@ -182,13 +145,26 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
// SEC-501 - should paths stored in request maps be converted to lower case
|
// SEC-501 - should paths stored in request maps be converted to lower case
|
||||||
// true if Ant path and using lower case
|
// true if Ant path and using lower case
|
||||||
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
|
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
|
||||||
|
|
||||||
parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
|
LinkedHashMap channelRequestMap = new LinkedHashMap();
|
||||||
filterChainMap, interceptorFilterInvDefSource, channelFilterInvDefSource,
|
LinkedHashMap filterInvocationDefinitionMap = new LinkedHashMap();
|
||||||
|
|
||||||
|
List interceptUrlElts = DomUtils.getChildElementsByTagName(element, "intercept-url");
|
||||||
|
parseInterceptUrlsForChannelSecurityAndFilterChain(interceptUrlElts, filterChainMap, channelRequestMap,
|
||||||
|
convertPathsToLowerCase, parserContext);
|
||||||
|
parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, filterInvocationDefinitionMap,
|
||||||
convertPathsToLowerCase, parserContext);
|
convertPathsToLowerCase, parserContext);
|
||||||
|
|
||||||
|
DefaultFilterInvocationDefinitionSource interceptorFilterInvDefSource =
|
||||||
|
new DefaultFilterInvocationDefinitionSource(matcher, filterInvocationDefinitionMap);
|
||||||
|
DefaultFilterInvocationDefinitionSource channelFilterInvDefSource =
|
||||||
|
new DefaultFilterInvocationDefinitionSource(matcher, channelRequestMap);
|
||||||
|
|
||||||
|
filterSecurityInterceptorBuilder.addPropertyValue("objectDefinitionSource", interceptorFilterInvDefSource);
|
||||||
|
|
||||||
|
|
||||||
// Check if we need to register the channel processing beans
|
// Check if we need to register the channel processing beans
|
||||||
if (((DefaultFilterInvocationDefinitionSource)channelFilterInvDefSource).getMapSize() > 0) {
|
if (channelRequestMap.size() > 0) {
|
||||||
// At least one channel requirement has been specified
|
// At least one channel requirement has been specified
|
||||||
RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class);
|
RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class);
|
||||||
channelFilter.getPropertyValues().addPropertyValue("channelDecisionManager",
|
channelFilter.getPropertyValues().addPropertyValue("channelDecisionManager",
|
||||||
@ -271,18 +247,103 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the intercept-url elements and populates the FilterChainProxy's filter chain Map and the
|
* Parses the intercept-url elements and populates the FilterChainProxy's filter chain Map and the
|
||||||
* FilterInvocationDefinitionSource used in FilterSecurityInterceptor.
|
* map used to create the FilterInvocationDefintionSource for the FilterSecurityInterceptor.
|
||||||
*/
|
*/
|
||||||
private void parseInterceptUrls(List urlElts, Map filterChainMap,
|
void parseInterceptUrlsForChannelSecurityAndFilterChain(List urlElts, Map filterChainMap, Map channelRequestMap,
|
||||||
DefaultFilterInvocationDefinitionSource interceptorFilterInvDefSource,
|
|
||||||
DefaultFilterInvocationDefinitionSource channelFilterInvDefSource,
|
|
||||||
boolean useLowerCasePaths, ParserContext parserContext) {
|
boolean useLowerCasePaths, ParserContext parserContext) {
|
||||||
|
|
||||||
Iterator urlEltsIterator = urlElts.iterator();
|
Iterator urlEltsIterator = urlElts.iterator();
|
||||||
|
ConfigAttributeEditor editor = new ConfigAttributeEditor();
|
||||||
|
|
||||||
|
while (urlEltsIterator.hasNext()) {
|
||||||
|
Element urlElt = (Element) urlEltsIterator.next();
|
||||||
|
|
||||||
|
String path = urlElt.getAttribute(ATT_PATH_PATTERN);
|
||||||
|
|
||||||
|
if(!StringUtils.hasText(path)) {
|
||||||
|
parserContext.getReaderContext().error("path attribute cannot be empty or null", urlElt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useLowerCasePaths) {
|
||||||
|
path = path.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
|
||||||
|
|
||||||
|
if (StringUtils.hasText(requiredChannel)) {
|
||||||
|
String channelConfigAttribute = null;
|
||||||
|
|
||||||
|
if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) {
|
||||||
|
channelConfigAttribute = "REQUIRES_SECURE_CHANNEL";
|
||||||
|
} else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) {
|
||||||
|
channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL";
|
||||||
|
} else if (requiredChannel.equals(OPT_ANY_CHANNEL)) {
|
||||||
|
channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL;
|
||||||
|
} else {
|
||||||
|
parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.setAsText(channelConfigAttribute);
|
||||||
|
channelRequestMap.put(new RequestKey(path), (ConfigAttributeDefinition) editor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
String filters = urlElt.getAttribute(ATT_FILTERS);
|
||||||
|
|
||||||
|
if (StringUtils.hasText(filters)) {
|
||||||
|
if (!filters.equals(OPT_FILTERS_NONE)) {
|
||||||
|
parserContext.getReaderContext().error("Currently only 'none' is supported as the custom " +
|
||||||
|
"filters attribute", urlElt);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChainMap.put(path, Collections.EMPTY_LIST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseInterceptUrlsForFilterInvocationRequestMap(List urlElts, Map filterInvocationDefinitionMap,
|
||||||
|
boolean useLowerCasePaths, ParserContext parserContext) {
|
||||||
|
|
||||||
|
Iterator urlEltsIterator = urlElts.iterator();
|
||||||
ConfigAttributeEditor editor = new ConfigAttributeEditor();
|
ConfigAttributeEditor editor = new ConfigAttributeEditor();
|
||||||
|
|
||||||
while (urlEltsIterator.hasNext()) {
|
while (urlEltsIterator.hasNext()) {
|
||||||
@ -308,38 +369,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
// Convert the comma-separated list of access attributes to a ConfigAttributeDefinition
|
// Convert the comma-separated list of access attributes to a ConfigAttributeDefinition
|
||||||
if (StringUtils.hasText(access)) {
|
if (StringUtils.hasText(access)) {
|
||||||
editor.setAsText(access);
|
editor.setAsText(access);
|
||||||
interceptorFilterInvDefSource.addSecureUrl(path, method, (ConfigAttributeDefinition) editor.getValue());
|
filterInvocationDefinitionMap.put(new RequestKey(path, method), editor.getValue());
|
||||||
}
|
|
||||||
|
|
||||||
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
|
|
||||||
|
|
||||||
if (StringUtils.hasText(requiredChannel)) {
|
|
||||||
String channelConfigAttribute = null;
|
|
||||||
|
|
||||||
if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) {
|
|
||||||
channelConfigAttribute = "REQUIRES_SECURE_CHANNEL";
|
|
||||||
} else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) {
|
|
||||||
channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL";
|
|
||||||
} else if (requiredChannel.equals(OPT_ANY_CHANNEL)) {
|
|
||||||
channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL;
|
|
||||||
} else {
|
|
||||||
parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt);
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.setAsText(channelConfigAttribute);
|
|
||||||
channelFilterInvDefSource.addSecureUrl(path, (ConfigAttributeDefinition) editor.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
String filters = urlElt.getAttribute(ATT_FILTERS);
|
|
||||||
|
|
||||||
if (StringUtils.hasText(filters)) {
|
|
||||||
if (!filters.equals(OPT_FILTERS_NONE)) {
|
|
||||||
parserContext.getReaderContext().error("Currently only 'none' is supported as the custom " +
|
|
||||||
"filters attribute", urlElt);
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChainMap.put(path, Collections.EMPTY_LIST);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
|
|||||||
registerBeanDefinitionParser(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
|
registerBeanDefinitionParser(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
|
||||||
registerBeanDefinitionParser(Elements.ANNOTATION_DRIVEN, new AnnotationDrivenBeanDefinitionParser());
|
registerBeanDefinitionParser(Elements.ANNOTATION_DRIVEN, new AnnotationDrivenBeanDefinitionParser());
|
||||||
registerBeanDefinitionParser(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
|
registerBeanDefinitionParser(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
|
||||||
|
registerBeanDefinitionParser(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationDefinitionSourceBeanDefinitionParser());
|
||||||
|
|
||||||
// Decorators
|
// Decorators
|
||||||
registerBeanDefinitionDecorator(Elements.INTERCEPT_METHODS, new InterceptMethodsBeanDefinitionDecorator());
|
registerBeanDefinitionDecorator(Elements.INTERCEPT_METHODS, new InterceptMethodsBeanDefinitionDecorator());
|
||||||
|
@ -61,7 +61,7 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Non method-specific map of URL patterns to <tt>ConfigAttributeDefinition</tt>s
|
* Non method-specific map of URL patterns to <tt>ConfigAttributeDefinition</tt>s
|
||||||
* TODO: Store in the httpMethod map with null key.
|
* TODO: Store in the httpMethod map with null key.
|
||||||
*/
|
*/
|
||||||
private Map requestMap = new LinkedHashMap();
|
private Map requestMap = new LinkedHashMap();
|
||||||
/** Stores request maps keyed by specific HTTP methods */
|
/** Stores request maps keyed by specific HTTP methods */
|
||||||
@ -75,10 +75,18 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
|
|||||||
* Creates a FilterInvocationDefinitionSource with the supplied URL matching strategy.
|
* Creates a FilterInvocationDefinitionSource with the supplied URL matching strategy.
|
||||||
* @param urlMatcher
|
* @param urlMatcher
|
||||||
*/
|
*/
|
||||||
public DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher) {
|
DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher) {
|
||||||
this.urlMatcher = urlMatcher;
|
this.urlMatcher = urlMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* the type of the supplied UrlMatcher.
|
||||||
|
*
|
||||||
|
* @param urlMatcher typically an ant or regular expression matcher.
|
||||||
|
* @param requestMap order-preserving map of <RequestKey, ConfigAttributeDefinition>.
|
||||||
|
*/
|
||||||
public DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher, LinkedHashMap requestMap) {
|
public DefaultFilterInvocationDefinitionSource(UrlMatcher urlMatcher, LinkedHashMap requestMap) {
|
||||||
this.urlMatcher = urlMatcher;
|
this.urlMatcher = urlMatcher;
|
||||||
|
|
||||||
@ -86,13 +94,14 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
|
|||||||
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Map.Entry entry = (Map.Entry) iterator.next();
|
Map.Entry entry = (Map.Entry) iterator.next();
|
||||||
addSecureUrl((String)entry.getKey(), (ConfigAttributeDefinition)entry.getValue());
|
RequestKey reqKey = (RequestKey) entry.getKey();
|
||||||
|
addSecureUrl(reqKey.getUrl(), reqKey.getMethod(), (ConfigAttributeDefinition) entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
|
|
||||||
public void addSecureUrl(String pattern, ConfigAttributeDefinition attr) {
|
void addSecureUrl(String pattern, ConfigAttributeDefinition attr) {
|
||||||
addSecureUrl(pattern, null, attr);
|
addSecureUrl(pattern, null, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +111,7 @@ public class DefaultFilterInvocationDefinitionSource implements FilterInvocation
|
|||||||
* to the request map and will be passed back to the <tt>UrlMatcher</tt> when iterating through the map to find
|
* 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.
|
* a match for a particular URL.
|
||||||
*/
|
*/
|
||||||
public void addSecureUrl(String pattern, String method, ConfigAttributeDefinition attr) {
|
void addSecureUrl(String pattern, String method, ConfigAttributeDefinition attr) {
|
||||||
Map mapToUse = getRequestMapForHttpMethod(method);
|
Map mapToUse = getRequestMapForHttpMethod(method);
|
||||||
|
|
||||||
mapToUse.put(urlMatcher.compile(pattern), attr);
|
mapToUse.put(urlMatcher.compile(pattern), attr);
|
||||||
|
@ -167,7 +167,7 @@ public class FilterInvocationDefinitionSourceEditor extends PropertyEditorSuppor
|
|||||||
|
|
||||||
String[] tokens = StringUtils.commaDelimitedListToStringArray(value);
|
String[] tokens = StringUtils.commaDelimitedListToStringArray(value);
|
||||||
|
|
||||||
urlMap.put(name, new ConfigAttributeDefinition(tokens));
|
urlMap.put(new RequestKey(name), new ConfigAttributeDefinition(tokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultFilterInvocationDefinitionSource fids =
|
DefaultFilterInvocationDefinitionSource fids =
|
||||||
|
@ -5,25 +5,35 @@ package org.springframework.security.intercept.web;
|
|||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class RequestKey {
|
public class RequestKey {
|
||||||
String url;
|
private String url;
|
||||||
String method;
|
private String method;
|
||||||
|
|
||||||
public RequestKey(String url) {
|
public RequestKey(String url) {
|
||||||
this(url, "");
|
this(url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestKey(String url, String method) {
|
public RequestKey(String url, String method) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int code = 31;
|
int code = 31;
|
||||||
code ^= url.hashCode();
|
code ^= url.hashCode();
|
||||||
code ^= method.hashCode();
|
|
||||||
|
if (method != null) {
|
||||||
|
code ^= method.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
return code;
|
return code;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
@ -33,6 +43,14 @@ public class RequestKey {
|
|||||||
|
|
||||||
RequestKey key = (RequestKey) obj;
|
RequestKey key = (RequestKey) obj;
|
||||||
|
|
||||||
return url.equals(key.url) && method.equals(key.method);
|
if (!url.equals(key.url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == null && key.method != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return method.equals(key.method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package org.springframework.security.util;
|
|||||||
/**
|
/**
|
||||||
* Strategy for deciding whether configured path matches a submitted candidate URL.
|
* Strategy for deciding whether configured path matches a submitted candidate URL.
|
||||||
*
|
*
|
||||||
* @author luke
|
* @author Luke Taylor
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
|
@ -212,6 +212,18 @@ filter-chain.attlist &=
|
|||||||
filter-chain.attlist &=
|
filter-chain.attlist &=
|
||||||
attribute filters {xsd:string}
|
attribute filters {xsd:string}
|
||||||
|
|
||||||
|
filter-invocation-definition-source =
|
||||||
|
## Used to explicitly configure a FilterInvocationDefinitionSource bean for use with a FilterSecurityInterceptor. Usually only needed if you are configuring a FilterChainProxy explicitly, rather than using the <http> element. The intercept-url elements used should only contain pattern, method and access attributes. Any others will result in a configuration error.
|
||||||
|
element filter-invocation-definition-source {fids.attlist, intercept-url+}
|
||||||
|
fids.attlist &=
|
||||||
|
id?
|
||||||
|
fids.attlist &=
|
||||||
|
## as for http element
|
||||||
|
attribute lowercase-comparisons {"true" | "false"}?
|
||||||
|
fids.attlist &=
|
||||||
|
## as for http element
|
||||||
|
path-type?
|
||||||
|
|
||||||
http-basic =
|
http-basic =
|
||||||
## Adds support for basic authentication (this is an element to permit future expansion, such as supporting an "ignoreFailure" attribute)
|
## Adds support for basic authentication (this is an element to permit future expansion, such as supporting an "ignoreFailure" attribute)
|
||||||
element http-basic {empty}
|
element http-basic {empty}
|
||||||
@ -328,6 +340,6 @@ before =
|
|||||||
## The filter immediately before which the custom-filter should be placed in the chain
|
## The filter immediately before which the custom-filter should be placed in the chain
|
||||||
attribute before {"FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "SESSION_CONTEXT_INTEGRATION_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_PROCESSING_FILTER" | "AUTHENTICATION_PROCESSING_FILTER" | "BASIC_PROCESSING_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "NTLM_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER"}
|
attribute before {"FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "SESSION_CONTEXT_INTEGRATION_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_PROCESSING_FILTER" | "AUTHENTICATION_PROCESSING_FILTER" | "BASIC_PROCESSING_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "NTLM_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -527,6 +527,46 @@
|
|||||||
<xs:attribute name="pattern" use="required" type="xs:string"/>
|
<xs:attribute name="pattern" use="required" type="xs:string"/>
|
||||||
<xs:attribute name="filters" use="required" type="xs:string"/>
|
<xs:attribute name="filters" use="required" type="xs:string"/>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
|
<xs:element name="filter-invocation-definition-source">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to explicitly configure a FilterInvocationDefinitionSource bean for use with a FilterSecurityInterceptor. Usually only needed if you are configuring a FilterChainProxy explicitly, rather than using the <http> element. The intercept-url elements used should only contain pattern, method and access attributes. Any others will result in a configuration error. </xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element maxOccurs="unbounded" ref="security:intercept-url"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attributeGroup ref="security:fids.attlist"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:attributeGroup name="fids.attlist">
|
||||||
|
<xs:attribute name="id" type="xs:ID">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A bean identifier, used for referring to the bean elsewhere in the context.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="lowercase-comparisons">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>as for http element</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="true"/>
|
||||||
|
<xs:enumeration value="false"/>
|
||||||
|
</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>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:token">
|
||||||
|
<xs:enumeration value="ant"/>
|
||||||
|
<xs:enumeration value="regex"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:attributeGroup>
|
||||||
<xs:element name="http-basic">
|
<xs:element name="http-basic">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Adds support for basic authentication (this is an element to permit future expansion, such as supporting an "ignoreFailure" attribute)</xs:documentation>
|
<xs:documentation>Adds support for basic authentication (this is an element to permit future expansion, such as supporting an "ignoreFailure" attribute)</xs:documentation>
|
||||||
|
@ -38,7 +38,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class HttpSecurityBeanDefinitionParserTests {
|
public class HttpSecurityBeanDefinitionParserTests {
|
||||||
private AbstractXmlApplicationContext appContext;
|
private AbstractXmlApplicationContext appContext;
|
||||||
private static final String AUTH_PROVIDER_XML =
|
static final String AUTH_PROVIDER_XML =
|
||||||
" <authentication-provider>" +
|
" <authentication-provider>" +
|
||||||
" <user-service>" +
|
" <user-service>" +
|
||||||
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
||||||
|
@ -19,6 +19,7 @@ import org.springframework.security.ConfigAttributeDefinition;
|
|||||||
import org.springframework.security.MockFilterChain;
|
import org.springframework.security.MockFilterChain;
|
||||||
import org.springframework.security.SecurityConfig;
|
import org.springframework.security.SecurityConfig;
|
||||||
import org.springframework.security.util.AntUrlPathMatcher;
|
import org.springframework.security.util.AntUrlPathMatcher;
|
||||||
|
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||||
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
@ -169,6 +170,38 @@ public class DefaultFilterInvocationDefinitionSourceTests {
|
|||||||
response = map.lookupAttributes(fi.getRequestUrl());
|
response = map.lookupAttributes(fi.getRequestUrl());
|
||||||
assertEquals(def, response);
|
assertEquals(def, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void xmlMapConfigurationIsSuccessful() {
|
||||||
|
InMemoryXmlApplicationContext context = new InMemoryXmlApplicationContext(
|
||||||
|
"<b:bean id='fids' class='org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource'>" +
|
||||||
|
" <b:constructor-arg>" +
|
||||||
|
" <b:bean class='org.springframework.security.util.AntUrlPathMatcher'/>" +
|
||||||
|
" </b:constructor-arg>" +
|
||||||
|
" <b:constructor-arg>" +
|
||||||
|
" <b:map>" +
|
||||||
|
" <b:entry>" +
|
||||||
|
" <b:key>" +
|
||||||
|
" <b:bean class='org.springframework.security.intercept.web.RequestKey'>" +
|
||||||
|
" <b:constructor-arg index='0' value='/**'/>" +
|
||||||
|
" <b:constructor-arg index='1' value='GET'/>" +
|
||||||
|
" </b:bean>" +
|
||||||
|
" </b:key>" +
|
||||||
|
" <b:bean class='org.springframework.security.ConfigAttributeDefinition'>" +
|
||||||
|
" <b:constructor-arg value='ROLE_A'/>" +
|
||||||
|
" </b:bean>" +
|
||||||
|
" </b:entry>" +
|
||||||
|
" </b:map>" +
|
||||||
|
" </b:constructor-arg>" +
|
||||||
|
"</b:bean>"
|
||||||
|
);
|
||||||
|
|
||||||
|
DefaultFilterInvocationDefinitionSource fids = (DefaultFilterInvocationDefinitionSource) context.getBean("fids");
|
||||||
|
ConfigAttributeDefinition cad = fids.lookupAttributes("/anything", "GET");
|
||||||
|
assertNotNull(cad);
|
||||||
|
assertEquals(1, cad.getConfigAttributes().size());
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
private FilterInvocation createFilterInvocation(String path, String method) {
|
private FilterInvocation createFilterInvocation(String path, String method) {
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
@ -39,6 +39,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
@ -220,9 +221,10 @@ public class FilterSecurityInterceptorTests extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testNotLoadedFromApplicationContext() throws Exception {
|
public void testNotLoadedFromApplicationContext() throws Exception {
|
||||||
|
LinkedHashMap reqMap = new LinkedHashMap();
|
||||||
|
reqMap.put(new RequestKey("/secure/**", null), new ConfigAttributeDefinition(new String[] {"ROLE_USER"}));
|
||||||
DefaultFilterInvocationDefinitionSource fids
|
DefaultFilterInvocationDefinitionSource fids
|
||||||
= new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher());
|
= new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher());
|
||||||
fids.addSecureUrl("/secure/**", null, new ConfigAttributeDefinition(new String[] {"ROLE_USER"}));
|
|
||||||
|
|
||||||
FilterSecurityInterceptor filter = new FilterSecurityInterceptor();
|
FilterSecurityInterceptor filter = new FilterSecurityInterceptor();
|
||||||
filter.setObjectDefinitionSource(fids);
|
filter.setObjectDefinitionSource(fids);
|
||||||
|
@ -21,6 +21,7 @@ import org.springframework.security.MockFilterConfig;
|
|||||||
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
||||||
import org.springframework.security.intercept.web.MockFilterInvocationDefinitionSource;
|
import org.springframework.security.intercept.web.MockFilterInvocationDefinitionSource;
|
||||||
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
|
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
|
||||||
|
import org.springframework.security.intercept.web.RequestKey;
|
||||||
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
@ -34,6 +35,7 @@ import static org.junit.Assert.*;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,9 +82,10 @@ public class FilterChainProxyTests {
|
|||||||
|
|
||||||
ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new MockConfigAttribute());
|
ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new MockConfigAttribute());
|
||||||
|
|
||||||
|
LinkedHashMap map = new LinkedHashMap();
|
||||||
|
map.put(new RequestKey("/**"), cad);
|
||||||
DefaultFilterInvocationDefinitionSource fids =
|
DefaultFilterInvocationDefinitionSource fids =
|
||||||
new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher());
|
new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(), map);
|
||||||
fids.addSecureUrl("/**", cad);
|
|
||||||
|
|
||||||
filterChainProxy.setFilterInvocationDefinitionSource(fids);
|
filterChainProxy.setFilterInvocationDefinitionSource(fids);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user