SEC-1256: Added support for expression attributes in filter-security-metadata-source configuration.

This commit is contained in:
Luke Taylor 2009-10-06 16:39:56 +00:00
parent caff3ee9ba
commit 5d486a51b6
4 changed files with 85 additions and 67 deletions

View File

@ -8,7 +8,7 @@ import org.springframework.security.config.authentication.JdbcUserServiceBeanDef
import org.springframework.security.config.authentication.UserServiceBeanDefinitionParser;
import org.springframework.security.config.http.CustomFilterBeanDefinitionDecorator;
import org.springframework.security.config.http.FilterChainMapBeanDefinitionDecorator;
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceBeanDefinitionParser;
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser;
import org.springframework.security.config.http.HttpSecurityBeanDefinitionParser;
import org.springframework.security.config.ldap.LdapProviderBeanDefinitionParser;
import org.springframework.security.config.ldap.LdapServerBeanDefinitionParser;
@ -39,8 +39,8 @@ public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
registerBeanDefinitionParser(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
registerBeanDefinitionParser(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
registerBeanDefinitionParser(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
registerBeanDefinitionParser(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceBeanDefinitionParser());
registerBeanDefinitionParser(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceBeanDefinitionParser());
registerBeanDefinitionParser(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
registerBeanDefinitionParser(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
// Decorators
registerBeanDefinitionDecorator(Elements.INTERCEPT_METHODS, new InterceptMethodsBeanDefinitionDecorator());

View File

@ -5,11 +5,17 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.Elements;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
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;
@ -24,18 +30,14 @@ import org.w3c.dom.Element;
* @author Luke Taylor
* @version $Id$
*/
public class FilterInvocationSecurityMetadataSourceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinitionParser {
private static final String ATT_USE_EXPRESSIONS = "use-expressions";
private static final String ATT_HTTP_METHOD = "method";
private static final String ATT_PATTERN = "pattern";
private static final String ATT_ACCESS = "access";
private static final Log logger = LogFactory.getLog(FilterInvocationSecurityMetadataSourceBeanDefinitionParser.class);
private static final Log logger = LogFactory.getLog(FilterInvocationSecurityMetadataSourceParser.class);
protected String getBeanClassName(Element element) {
return "org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource";
}
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
public BeanDefinition parse(Element element, ParserContext parserContext) {
List<Element> interceptUrls = DomUtils.getChildElementsByTagName(element, "intercept-url");
// Check for attributes that aren't allowed in this context
@ -49,17 +51,60 @@ public class FilterInvocationSecurityMetadataSourceBeanDefinitionParser extends
}
}
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(element);
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
BeanDefinition mds = createSecurityMetadataSource(interceptUrls, element, parserContext);
ManagedMap<BeanDefinition, BeanDefinition> requestMap = parseInterceptUrlsForFilterInvocationRequestMap(
interceptUrls, convertPathsToLowerCase, false, parserContext);
String id = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE);
builder.addConstructorArgValue(matcher);
builder.addConstructorArgValue(requestMap);
if (StringUtils.hasText(id)) {
parserContext.registerComponent(new BeanComponentDefinition(mds, id));
parserContext.getRegistry().registerBeanDefinition(id, mds);
}
return mds;
}
static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts,
static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(elt);
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
boolean useExpressions = isUseExpressions(elt);
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
interceptUrls, convertPathsToLowerCase, useExpressions, pc);
BeanDefinitionBuilder fidsBuilder;
if (useExpressions) {
Element expressionHandlerElt = DomUtils.getChildElementByTagName(elt, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
} else {
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultWebSecurityExpressionHandler.class).getBeanDefinition();
expressionHandlerRef = pc.getReaderContext().registerWithGeneratedName(expressionHandler);
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
}
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.getRawBeanDefinition().setSource(pc.extractSource(elt));
return fidsBuilder.getBeanDefinition();
}
static boolean isUseExpressions(Element elt) {
return "true".equals(elt.getAttribute(ATT_USE_EXPRESSIONS));
}
private static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts,
boolean useLowerCasePaths, boolean useExpressions, ParserContext parserContext) {
ManagedMap<BeanDefinition, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanDefinition, BeanDefinition>();
@ -114,4 +159,6 @@ public class FilterInvocationSecurityMetadataSourceBeanDefinitionParser extends
return filterInvocationDefinitionMap;
}
}

View File

@ -78,7 +78,6 @@ class HttpConfigurationBuilder {
private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting";
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
private static final String ATT_USE_EXPRESSIONS = "use-expressions";
private static final String ATT_ONCE_PER_REQUEST = "once-per-request";
private final Element httpElt;
@ -415,45 +414,21 @@ class HttpConfigurationBuilder {
}
void createFilterSecurityInterceptor(BeanReference authManager) {
BeanDefinitionBuilder fidsBuilder;
boolean useExpressions = "true".equals(httpElt.getAttribute(ATT_USE_EXPRESSIONS));
ManagedMap<BeanDefinition,BeanDefinition> requestToAttributesMap =
parseInterceptUrlsForFilterInvocationRequestMap(DomUtils.getChildElementsByTagName(httpElt, Elements.INTERCEPT_URL),
convertPathsToLowerCase, useExpressions, pc);
boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
RootBeanDefinition accessDecisionMgr;
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(2);
if (useExpressions) {
Element expressionHandlerElt = DomUtils.getChildElementByTagName(httpElt, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
} else {
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_HANDLER_CLASS).getBeanDefinition();
expressionHandlerRef = pc.getReaderContext().registerWithGeneratedName(expressionHandler);
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
}
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_FIMDS_CLASS);
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
voters.add(new RootBeanDefinition(WebExpressionVoter.class));
} else {
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
}
accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
accessDecisionMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);
accessDecisionMgr.setSource(pc.extractSource(httpElt));
fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
// Set up the access manager reference for http
String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);
@ -472,7 +447,7 @@ class HttpConfigurationBuilder {
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
}
builder.addPropertyValue("securityMetadataSource", fidsBuilder.getBeanDefinition());
builder.addPropertyValue("securityMetadataSource", securityMds);
BeanDefinition fsiBean = builder.getBeanDefinition();
String fsiId = pc.getReaderContext().registerWithGeneratedName(fsiBean);
pc.registerBeanComponent(new BeanComponentDefinition(fsiBean,fsiId));
@ -486,18 +461,6 @@ class HttpConfigurationBuilder {
this.fsi = new RuntimeBeanReference(fsiId);
}
/**
* Parses the filter invocation map which will be used to configure the FilterInvocationSecurityMetadataSource
* used in the security interceptor.
*/
private static ManagedMap<BeanDefinition,BeanDefinition>
parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts, boolean useLowerCasePaths,
boolean useExpressions, ParserContext parserContext) {
return FilterInvocationSecurityMetadataSourceBeanDefinitionParser.parseInterceptUrlsForFilterInvocationRequestMap(urlElts, useLowerCasePaths, useExpressions, parserContext);
}
BeanReference getSessionStrategy() {
return sessionStrategyRef;
}

View File

@ -1,7 +1,6 @@
package org.springframework.security.config.http;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.List;
@ -15,14 +14,13 @@ import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.ConfigTestUtils;
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceBeanDefinitionParser;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.w3c.dom.Element;
/**
* Tests for {@link FilterInvocationSecurityMetadataSourceBeanDefinitionParser}.
* Tests for {@link FilterInvocationSecurityMetadataSourceParser}.
* @author Luke Taylor
* @version $Id$
*/
@ -41,10 +39,6 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
appContext = new InMemoryXmlApplicationContext(context);
}
@Test
public void beanClassNameIsCorrect() throws Exception {
assertEquals(DefaultFilterInvocationSecurityMetadataSource.class.getName(), new FilterInvocationSecurityMetadataSourceBeanDefinitionParser().getBeanClassName(mock(Element.class)));
}
@Test
public void parsingMinimalConfigurationIsSuccessful() {
@ -58,6 +52,20 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
assertTrue(cad.contains(new SecurityConfig("ROLE_A")));
}
@Test
public void expressionsAreSupported() {
setContext(
"<filter-security-metadata-source id='fids' use-expressions='true'>" +
" <intercept-url pattern='/**' access=\"hasRole('ROLE_A')\" />" +
"</filter-security-metadata-source>");
ExpressionBasedFilterInvocationSecurityMetadataSource fids =
(ExpressionBasedFilterInvocationSecurityMetadataSource) appContext.getBean("fids");
List<? extends ConfigAttribute> cad = fids.getAttributes(createFilterInvocation("/anything", "GET"));
assertEquals(1, cad.size());
assertEquals("hasRole('ROLE_A')", cad.get(0).toString());
}
// SEC-1201
@Test
public void interceptUrlsSupportPropertyPlaceholders() {