Skip to content

Commit 5d486a5

Browse files
committed
SEC-1256: Added support for expression attributes in filter-security-metadata-source configuration.
1 parent caff3ee commit 5d486a5

File tree

4 files changed

+84
-66
lines changed

4 files changed

+84
-66
lines changed

config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import org.springframework.security.config.authentication.UserServiceBeanDefinitionParser;
99
import org.springframework.security.config.http.CustomFilterBeanDefinitionDecorator;
1010
import org.springframework.security.config.http.FilterChainMapBeanDefinitionDecorator;
11-
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceBeanDefinitionParser;
11+
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser;
1212
import org.springframework.security.config.http.HttpSecurityBeanDefinitionParser;
1313
import org.springframework.security.config.ldap.LdapProviderBeanDefinitionParser;
1414
import org.springframework.security.config.ldap.LdapServerBeanDefinitionParser;
@@ -39,8 +39,8 @@ public void init() {
3939
registerBeanDefinitionParser(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
4040
registerBeanDefinitionParser(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
4141
registerBeanDefinitionParser(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
42-
registerBeanDefinitionParser(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceBeanDefinitionParser());
43-
registerBeanDefinitionParser(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceBeanDefinitionParser());
42+
registerBeanDefinitionParser(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
43+
registerBeanDefinitionParser(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
4444

4545
// Decorators
4646
registerBeanDefinitionDecorator(Elements.INTERCEPT_METHODS, new InterceptMethodsBeanDefinitionDecorator());
Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
import org.apache.commons.logging.Log;
66
import org.apache.commons.logging.LogFactory;
77
import org.springframework.beans.factory.config.BeanDefinition;
8+
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
89
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
910
import org.springframework.beans.factory.support.ManagedMap;
10-
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
11+
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
12+
import org.springframework.beans.factory.xml.BeanDefinitionParser;
1113
import org.springframework.beans.factory.xml.ParserContext;
1214
import org.springframework.security.access.SecurityConfig;
15+
import org.springframework.security.config.Elements;
16+
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
17+
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
18+
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
1319
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
1420
import org.springframework.security.web.access.intercept.RequestKey;
1521
import org.springframework.security.web.util.AntUrlPathMatcher;
@@ -24,18 +30,14 @@
2430
* @author Luke Taylor
2531
* @version $Id$
2632
*/
27-
public class FilterInvocationSecurityMetadataSourceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
28-
33+
public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinitionParser {
34+
private static final String ATT_USE_EXPRESSIONS = "use-expressions";
2935
private static final String ATT_HTTP_METHOD = "method";
3036
private static final String ATT_PATTERN = "pattern";
3137
private static final String ATT_ACCESS = "access";
32-
private static final Log logger = LogFactory.getLog(FilterInvocationSecurityMetadataSourceBeanDefinitionParser.class);
33-
34-
protected String getBeanClassName(Element element) {
35-
return "org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource";
36-
}
38+
private static final Log logger = LogFactory.getLog(FilterInvocationSecurityMetadataSourceParser.class);
3739

38-
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
40+
public BeanDefinition parse(Element element, ParserContext parserContext) {
3941
List<Element> interceptUrls = DomUtils.getChildElementsByTagName(element, "intercept-url");
4042

4143
// Check for attributes that aren't allowed in this context
@@ -49,17 +51,60 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
4951
}
5052
}
5153

52-
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(element);
54+
BeanDefinition mds = createSecurityMetadataSource(interceptUrls, element, parserContext);
55+
56+
String id = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE);
57+
58+
if (StringUtils.hasText(id)) {
59+
parserContext.registerComponent(new BeanComponentDefinition(mds, id));
60+
parserContext.getRegistry().registerBeanDefinition(id, mds);
61+
}
62+
63+
return mds;
64+
}
65+
66+
static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {
67+
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(elt);
5368
boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl();
69+
boolean useExpressions = isUseExpressions(elt);
70+
71+
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
72+
interceptUrls, convertPathsToLowerCase, useExpressions, pc);
73+
BeanDefinitionBuilder fidsBuilder;
5474

55-
ManagedMap<BeanDefinition, BeanDefinition> requestMap = parseInterceptUrlsForFilterInvocationRequestMap(
56-
interceptUrls, convertPathsToLowerCase, false, parserContext);
75+
if (useExpressions) {
76+
Element expressionHandlerElt = DomUtils.getChildElementByTagName(elt, Elements.EXPRESSION_HANDLER);
77+
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
5778

58-
builder.addConstructorArgValue(matcher);
59-
builder.addConstructorArgValue(requestMap);
79+
if (StringUtils.hasText(expressionHandlerRef)) {
80+
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
81+
} else {
82+
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultWebSecurityExpressionHandler.class).getBeanDefinition();
83+
expressionHandlerRef = pc.getReaderContext().registerWithGeneratedName(expressionHandler);
84+
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
85+
}
86+
87+
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
88+
fidsBuilder.addConstructorArgValue(matcher);
89+
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
90+
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
91+
} else {
92+
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
93+
fidsBuilder.addConstructorArgValue(matcher);
94+
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
95+
}
96+
97+
fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
98+
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));
99+
100+
return fidsBuilder.getBeanDefinition();
101+
}
102+
103+
static boolean isUseExpressions(Element elt) {
104+
return "true".equals(elt.getAttribute(ATT_USE_EXPRESSIONS));
60105
}
61106

62-
static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts,
107+
private static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts,
63108
boolean useLowerCasePaths, boolean useExpressions, ParserContext parserContext) {
64109

65110
ManagedMap<BeanDefinition, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanDefinition, BeanDefinition>();
@@ -114,4 +159,6 @@ static ManagedMap<BeanDefinition, BeanDefinition> parseInterceptUrlsForFilterInv
114159

115160
return filterInvocationDefinitionMap;
116161
}
162+
163+
117164
}

config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ class HttpConfigurationBuilder {
7878
private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting";
7979

8080
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
81-
private static final String ATT_USE_EXPRESSIONS = "use-expressions";
8281
private static final String ATT_ONCE_PER_REQUEST = "once-per-request";
8382

8483
private final Element httpElt;
@@ -415,45 +414,21 @@ private ManagedMap<BeanDefinition,BeanDefinition> parseInterceptUrlsForChannelSe
415414
}
416415

417416
void createFilterSecurityInterceptor(BeanReference authManager) {
418-
BeanDefinitionBuilder fidsBuilder;
419-
420-
boolean useExpressions = "true".equals(httpElt.getAttribute(ATT_USE_EXPRESSIONS));
421-
422-
ManagedMap<BeanDefinition,BeanDefinition> requestToAttributesMap =
423-
parseInterceptUrlsForFilterInvocationRequestMap(DomUtils.getChildElementsByTagName(httpElt, Elements.INTERCEPT_URL),
424-
convertPathsToLowerCase, useExpressions, pc);
417+
boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
418+
BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
425419

426420
RootBeanDefinition accessDecisionMgr;
427421
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(2);
428422

429423
if (useExpressions) {
430-
Element expressionHandlerElt = DomUtils.getChildElementByTagName(httpElt, Elements.EXPRESSION_HANDLER);
431-
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
432-
433-
if (StringUtils.hasText(expressionHandlerRef)) {
434-
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
435-
} else {
436-
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_HANDLER_CLASS).getBeanDefinition();
437-
expressionHandlerRef = pc.getReaderContext().registerWithGeneratedName(expressionHandler);
438-
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
439-
}
440-
441-
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_FIMDS_CLASS);
442-
fidsBuilder.addConstructorArgValue(matcher);
443-
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
444-
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
445424
voters.add(new RootBeanDefinition(WebExpressionVoter.class));
446425
} else {
447-
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
448-
fidsBuilder.addConstructorArgValue(matcher);
449-
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
450426
voters.add(new RootBeanDefinition(RoleVoter.class));
451427
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
452428
}
453429
accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
454430
accessDecisionMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);
455431
accessDecisionMgr.setSource(pc.extractSource(httpElt));
456-
fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
457432

458433
// Set up the access manager reference for http
459434
String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);
@@ -472,7 +447,7 @@ void createFilterSecurityInterceptor(BeanReference authManager) {
472447
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
473448
}
474449

475-
builder.addPropertyValue("securityMetadataSource", fidsBuilder.getBeanDefinition());
450+
builder.addPropertyValue("securityMetadataSource", securityMds);
476451
BeanDefinition fsiBean = builder.getBeanDefinition();
477452
String fsiId = pc.getReaderContext().registerWithGeneratedName(fsiBean);
478453
pc.registerBeanComponent(new BeanComponentDefinition(fsiBean,fsiId));
@@ -486,18 +461,6 @@ void createFilterSecurityInterceptor(BeanReference authManager) {
486461
this.fsi = new RuntimeBeanReference(fsiId);
487462
}
488463

489-
/**
490-
* Parses the filter invocation map which will be used to configure the FilterInvocationSecurityMetadataSource
491-
* used in the security interceptor.
492-
*/
493-
private static ManagedMap<BeanDefinition,BeanDefinition>
494-
parseInterceptUrlsForFilterInvocationRequestMap(List<Element> urlElts, boolean useLowerCasePaths,
495-
boolean useExpressions, ParserContext parserContext) {
496-
497-
return FilterInvocationSecurityMetadataSourceBeanDefinitionParser.parseInterceptUrlsForFilterInvocationRequestMap(urlElts, useLowerCasePaths, useExpressions, parserContext);
498-
499-
}
500-
501464
BeanReference getSessionStrategy() {
502465
return sessionStrategyRef;
503466
}

config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.springframework.security.config.http;
22

33
import static org.junit.Assert.*;
4-
import static org.mockito.Mockito.*;
54

65
import java.util.List;
76

@@ -15,14 +14,13 @@
1514
import org.springframework.security.access.SecurityConfig;
1615
import org.springframework.security.config.BeanIds;
1716
import org.springframework.security.config.ConfigTestUtils;
18-
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceBeanDefinitionParser;
1917
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
2018
import org.springframework.security.web.FilterInvocation;
19+
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
2120
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
22-
import org.w3c.dom.Element;
2321

2422
/**
25-
* Tests for {@link FilterInvocationSecurityMetadataSourceBeanDefinitionParser}.
23+
* Tests for {@link FilterInvocationSecurityMetadataSourceParser}.
2624
* @author Luke Taylor
2725
* @version $Id$
2826
*/
@@ -41,10 +39,6 @@ private void setContext(String context) {
4139
appContext = new InMemoryXmlApplicationContext(context);
4240
}
4341

44-
@Test
45-
public void beanClassNameIsCorrect() throws Exception {
46-
assertEquals(DefaultFilterInvocationSecurityMetadataSource.class.getName(), new FilterInvocationSecurityMetadataSourceBeanDefinitionParser().getBeanClassName(mock(Element.class)));
47-
}
4842

4943
@Test
5044
public void parsingMinimalConfigurationIsSuccessful() {
@@ -58,6 +52,20 @@ public void parsingMinimalConfigurationIsSuccessful() {
5852
assertTrue(cad.contains(new SecurityConfig("ROLE_A")));
5953
}
6054

55+
@Test
56+
public void expressionsAreSupported() {
57+
setContext(
58+
"<filter-security-metadata-source id='fids' use-expressions='true'>" +
59+
" <intercept-url pattern='/**' access=\"hasRole('ROLE_A')\" />" +
60+
"</filter-security-metadata-source>");
61+
62+
ExpressionBasedFilterInvocationSecurityMetadataSource fids =
63+
(ExpressionBasedFilterInvocationSecurityMetadataSource) appContext.getBean("fids");
64+
List<? extends ConfigAttribute> cad = fids.getAttributes(createFilterInvocation("/anything", "GET"));
65+
assertEquals(1, cad.size());
66+
assertEquals("hasRole('ROLE_A')", cad.get(0).toString());
67+
}
68+
6169
// SEC-1201
6270
@Test
6371
public void interceptUrlsSupportPropertyPlaceholders() {

0 commit comments

Comments
 (0)