SEC-1256: Added support for expression attributes in filter-security-metadata-source configuration.
This commit is contained in:
parent
caff3ee9ba
commit
5d486a51b6
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue