diff --git a/core/src/main/java/org/springframework/security/config/ConfigUtils.java b/core/src/main/java/org/springframework/security/config/ConfigUtils.java index eebfa2311e..2a12b20667 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -15,6 +15,7 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.afterinvocation.AfterInvocationProviderManager; import org.springframework.security.expression.method.MethodExpressionVoter; import org.springframework.security.util.UrlUtils; +import org.springframework.security.vote.AccessDecisionVoter; import org.springframework.security.vote.AffirmativeBased; import org.springframework.security.vote.AuthenticatedVoter; import org.springframework.security.vote.RoleVoter; @@ -44,10 +45,10 @@ abstract class ConfigUtils { } } - private static BeanDefinition createAccessManagerBean(Class... voters) { + private static BeanDefinition createAccessManagerBean(Class... voters) { ManagedList defaultVoters = new ManagedList(voters.length); - for(Class voter : voters) { + for(Class voter : voters) { defaultVoters.add(new RootBeanDefinition(voter)); } @@ -80,10 +81,11 @@ abstract class ConfigUtils { } BeanDefinition authManager = new RootBeanDefinition(NamespaceAuthenticationManager.class); - authManager.getPropertyValues().addPropertyValue("providerBeanNames", new ArrayList()); + authManager.getPropertyValues().addPropertyValue("providerBeanNames", new ArrayList()); parserContext.getRegistry().registerBeanDefinition(BeanIds.AUTHENTICATION_MANAGER, authManager); } + @SuppressWarnings("unchecked") static void addAuthenticationProvider(ParserContext parserContext, String beanName) { registerProviderManagerIfNecessary(parserContext); BeanDefinition authManager = parserContext.getRegistry().getBeanDefinition(BeanIds.AUTHENTICATION_MANAGER); diff --git a/core/src/main/java/org/springframework/security/config/Elements.java b/core/src/main/java/org/springframework/security/config/Elements.java index 9d1e8ea577..1c5d6a8001 100644 --- a/core/src/main/java/org/springframework/security/config/Elements.java +++ b/core/src/main/java/org/springframework/security/config/Elements.java @@ -20,7 +20,7 @@ abstract class Elements { public static final String LDAP_SERVER = "ldap-server"; public static final String LDAP_USER_SERVICE = "ldap-user-service"; public static final String PROTECT_POINTCUT = "protect-pointcut"; - public static final String PERMISSON_EVALUATOR = "permission-evaluator"; + public static final String EXPRESSION_HANDLER = "expression-handler"; public static final String PROTECT = "protect"; public static final String CONCURRENT_SESSIONS = "concurrent-session-control"; public static final String LOGOUT = "logout"; diff --git a/core/src/main/java/org/springframework/security/config/FilterInvocationDefinitionSourceBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/FilterInvocationDefinitionSourceBeanDefinitionParser.java index 02deb99025..20a6ac91ec 100644 --- a/core/src/main/java/org/springframework/security/config/FilterInvocationDefinitionSourceBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/FilterInvocationDefinitionSourceBeanDefinitionParser.java @@ -1,12 +1,14 @@ 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.ConfigAttribute; +import org.springframework.security.intercept.web.FilterInvocationDefinitionSource; +import org.springframework.security.intercept.web.RequestKey; import org.springframework.security.util.AntUrlPathMatcher; import org.springframework.security.util.UrlMatcher; import org.springframework.util.StringUtils; @@ -14,8 +16,8 @@ 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. - * + * Allows for convenient creation of a {@link FilterInvocationDefinitionSource} bean for use with a FilterSecurityInterceptor. + * * @author Luke Taylor * @version $Id$ */ @@ -26,12 +28,10 @@ public class FilterInvocationDefinitionSourceBeanDefinitionParser extends Abstra } protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - List interceptUrls = DomUtils.getChildElementsByTagName(element, "intercept-url"); - + 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(); + for(Element elt : interceptUrls) { if (StringUtils.hasLength(elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL))) { parserContext.getReaderContext().error("The attribute '" + HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL + "' isn't allowed here.", elt); } @@ -43,12 +43,12 @@ public class FilterInvocationDefinitionSourceBeanDefinitionParser extends Abstra UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(element); boolean convertPathsToLowerCase = (matcher instanceof AntUrlPathMatcher) && matcher.requiresLowerCaseUrl(); - - LinkedHashMap requestMap = - HttpSecurityBeanDefinitionParser.parseInterceptUrlsForFilterInvocationRequestMap(interceptUrls, - convertPathsToLowerCase, parserContext); - - builder.addConstructorArg(matcher); - builder.addConstructorArg(requestMap); + + LinkedHashMap> requestMap = + HttpSecurityBeanDefinitionParser.parseInterceptUrlsForFilterInvocationRequestMap(interceptUrls, + convertPathsToLowerCase, false, parserContext); + + builder.addConstructorArgValue(matcher); + builder.addConstructorArgValue(requestMap); } } diff --git a/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java index a656c12d34..dceacde27a 100644 --- a/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java @@ -30,6 +30,7 @@ import org.springframework.security.intercept.method.aopalliance.MethodSecurityI import org.springframework.security.vote.AffirmativeBased; import org.springframework.security.vote.AuthenticatedVoter; import org.springframework.security.vote.RoleVoter; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -56,8 +57,8 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String SECURITY_INTERCEPTOR_ID = "_globalMethodSecurityInterceptor"; static final String INTERCEPTOR_POST_PROCESSOR_ID = "_globalMethodSecurityInterceptorPostProcessor"; static final String ACCESS_MANAGER_ID = "_globalMethodSecurityAccessManager"; - static final String DELEGATING_METHOD_DEFINITION_SOURCE_ID = "_delegatingMethodDefinitionSource"; - static final String EXPRESSION_HANDLER_ID = "_expressionHandler"; + private static final String DELEGATING_METHOD_DEFINITION_SOURCE_ID = "_delegatingMethodDefinitionSource"; + private static final String EXPRESSION_HANDLER_ID = "_expressionHandler"; private static final String ATT_ACCESS = "access"; private static final String ATT_EXPRESSION = "expression"; @@ -122,28 +123,26 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { * provider will also be registered to handle post-invocation filtering and authorization expression annotations. */ private void registerAccessManager(Element element, ParserContext pc, boolean jsr250Enabled, boolean expressionsEnabled) { - Element permissionEvaluatorElt = DomUtils.getChildElementByTagName(element, Elements.PERMISSON_EVALUATOR); + Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER); BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); ManagedList voters = new ManagedList(4); if (expressionsEnabled) { - BeanDefinitionBuilder expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityExpressionHandler.class); BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class); BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class); + String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref"); - if (permissionEvaluatorElt != null) { - String ref = permissionEvaluatorElt.getAttribute("ref"); - logger.info("Using bean '" + ref + "' as PermissionEvaluator implementation"); - expressionHandler.addPropertyReference("permissionEvaluator", ref); + if (StringUtils.hasText(expressionHandlerRef)) { + logger.info("Using bean '" + expressionHandlerRef + "' as SecurityExpressionHandler implementation"); } else { - logger.warn("Expressions were enabled but no PermissionEvaluator was configured. " + + pc.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, new RootBeanDefinition(DefaultSecurityExpressionHandler.class)); + logger.warn("Expressions were enabled but no SecurityExpressionHandler was configured. " + "All hasPermision() expressions will evaluate to false."); + expressionHandlerRef = EXPRESSION_HANDLER_ID; } - pc.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, expressionHandler.getBeanDefinition()); - - expressionVoter.addPropertyReference("expressionHandler", EXPRESSION_HANDLER_ID); - afterInvocationProvider.addPropertyReference("expressionHandler", EXPRESSION_HANDLER_ID); + expressionVoter.addPropertyReference("expressionHandler", expressionHandlerRef); + afterInvocationProvider.addPropertyReference("expressionHandler", expressionHandlerRef); ConfigUtils.getRegisteredAfterInvocationProviders(pc).add(afterInvocationProvider.getBeanDefinition()); voters.add(expressionVoter.getBeanDefinition()); } diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index bc1a1a7c11..3778e6b5c4 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -16,7 +16,9 @@ import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeEditor; +import org.springframework.security.SecurityConfig; import org.springframework.security.context.HttpSessionContextIntegrationFilter; import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource; import org.springframework.security.intercept.web.FilterSecurityInterceptor; @@ -97,6 +99,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String ATT_ONCE_PER_REQUEST = "once-per-request"; static final String ATT_ACCESS_DENIED_PAGE = "access-denied-page"; + static final String ATT_USE_EXPRESSIONS = "use-expressions"; + static final String DEF_USE_EXPRESSIONS = "false"; + public BeanDefinition parse(Element element, ParserContext parserContext) { ConfigUtils.registerProviderManagerIfNecessary(parserContext); final BeanDefinitionRegistry registry = parserContext.getRegistry(); @@ -141,7 +146,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { } registerFilterSecurityInterceptor(element, parserContext, matcher, accessManagerId, - parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, parserContext)); + parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, false, parserContext)); boolean sessionControlEnabled = registerConcurrentSessionControlBeansIfRequired(element, parserContext); @@ -576,14 +581,21 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { } } - static LinkedHashMap parseInterceptUrlsForFilterInvocationRequestMap(List urlElts, boolean useLowerCasePaths, ParserContext parserContext) { - LinkedHashMap filterInvocationDefinitionMap = new LinkedHashMap(); + /** + * Parses the filter invocation map which will be used to configure the FilterInvocationDefinitionSource + * used in the security interceptor. + */ + static LinkedHashMap> + parseInterceptUrlsForFilterInvocationRequestMap(List urlElts, boolean useLowerCasePaths, + boolean useExpressions, ParserContext parserContext) { - Iterator urlEltsIterator = urlElts.iterator(); - ConfigAttributeEditor editor = new ConfigAttributeEditor(); + LinkedHashMap> filterInvocationDefinitionMap = new LinkedHashMap>(); - while (urlEltsIterator.hasNext()) { - Element urlElt = (Element) urlEltsIterator.next(); + for (Element urlElt : urlElts) { + String access = urlElt.getAttribute(ATT_ACCESS_CONFIG); + if (!StringUtils.hasText(access)) { + continue; + } String path = urlElt.getAttribute(ATT_PATH_PATTERN); @@ -600,19 +612,23 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { method = null; } - String access = urlElt.getAttribute(ATT_ACCESS_CONFIG); - // Convert the comma-separated list of access attributes to a List - if (StringUtils.hasText(access)) { - editor.setAsText(access); - Object key = new RequestKey(path, method); - if (filterInvocationDefinitionMap.containsKey(key)) { - logger.warn("Duplicate URL defined: " + key + ". The original attribute values will be overwritten"); - } + RequestKey key = new RequestKey(path, method); + List attributes = null; - filterInvocationDefinitionMap.put(key, editor.getValue()); + if (useExpressions) { + logger.info("Creating access control expression attribute '" + access + "' for " + key); + + } else { + attributes = SecurityConfig.createList(StringUtils.commaDelimitedListToStringArray(access)); + } + + if (filterInvocationDefinitionMap.containsKey(key)) { + logger.warn("Duplicate URL defined: " + key + ". The original attribute values will be overwritten"); } + + filterInvocationDefinitionMap.put(key, attributes); } return filterInvocationDefinitionMap; diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc index 468249df35..2a43a0b8b4 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc @@ -60,7 +60,10 @@ boolean = "true" | "false" role-prefix = ## A non-empty string prefix that will be added to role strings loaded from persistent storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is non-empty. attribute role-prefix {xsd:string} - + +use-expressions = + ## Enables the use of expressions in the 'access' attributes in elements rather than the traditional list of configuration attributes. Defaults to 'false'. If enabled, each attribute should contain a single boolean expression. If the expression evaluates to 'true', access will be granted. + attribute use-expressions {boolean} ldap-server = ## Defines an LDAP server location or starts an embedded server. The url indicates the location of a remote server. If no url is given, an embedded server will be started, listening on the supplied port number. The port is optional and defaults to 33389. A Spring LDAP ContextSource bean will be registered for the server with the id supplied. @@ -181,7 +184,7 @@ protect.attlist &= global-method-security = ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250. - element global-method-security {global-method-security.attlist, permission-evaluator?, protect-pointcut*} + element global-method-security {global-method-security.attlist, expression-handler?, protect-pointcut*} global-method-security.attlist &= ## Specifies whether the use of Spring Security's expression-based annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "disabled". attribute expression-annotations {"disabled" | "enabled" }? @@ -195,9 +198,9 @@ global-method-security.attlist &= ## Optional AccessDecisionManager bean ID to override the default used for method security. attribute access-decision-manager-ref {xsd:string}? -permission-evaluator = - ## Defines the PermissionEvaluator implementation which will be used to evaluate calls to hasPermission() expressions - element permission-evaluator {ref} +expression-handler = + ## Defines the SecurityExpressionHandler instance which will be used if expression-based access-control is enabled. A default implementation (with no ACL support) will be used if not supplied. + element expression-handler {ref} custom-after-invocation-provider = ## Used to decorate an AfterInvocationProvider to specify that it should be used with method security. @@ -220,6 +223,8 @@ http = http.attlist &= ## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false". attribute auto-config {boolean}? +http.attlist &= + use-expressions? http.attlist &= ## Controls the eagerness with which an HTTP session is created. If not set, defaults to "ifRequired". attribute create-session {"ifRequired" | "always" | "never" }? @@ -325,6 +330,8 @@ filter-chain.attlist &= 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 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 &= + use-expressions? fids.attlist &= id? fids.attlist &= diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd index e9810ff814..c298b5808a 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd @@ -168,6 +168,17 @@ + + + + Enables the use of expressions in the 'access' attributes in + <intercept-url> elements rather than the traditional list of configuration + attributes. Defaults to 'false'. If enabled, each attribute should contain a single + boolean expression. If the expression evaluates to 'true', access will be granted. + + + + Defines an LDAP server location or starts an embedded server. The url @@ -565,7 +576,7 @@ - + Defines a protected pointcut and the access control configuration @@ -627,10 +638,11 @@ - + - Defines the PermissionEvaluator implementation which will be used to - evaluate calls to hasPermission() expressions + Defines the SecurityExpressionHandler instance which will be used if + expression-based access-control is enabled. A default implementation (with no ACL support) + will be used if not supplied. @@ -763,6 +775,15 @@ "false". + + + Enables the use of expressions in the 'access' attributes in + <intercept-url> elements rather than the traditional list of configuration + attributes. Defaults to 'false'. If enabled, each attribute should contain a single + boolean expression. If the expression evaluates to 'true', access will be granted. + + + Controls the eagerness with which an HTTP session is created. If not set, @@ -1036,6 +1057,15 @@ + + + Enables the use of expressions in the 'access' attributes in + <intercept-url> elements rather than the traditional list of configuration + attributes. Defaults to 'false'. If enabled, each attribute should contain a single + boolean expression. If the expression evaluates to 'true', access will be granted. + + + A bean identifier, used for referring to the bean elsewhere in the diff --git a/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml b/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml index 5157ae0984..f84d64e30c 100644 --- a/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml +++ b/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml @@ -15,7 +15,7 @@ http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.5.xsd"> - + @@ -47,8 +47,12 @@ + + + + - + diff --git a/samples/contacts/src/test/resources/applicationContext-contacts-test.xml b/samples/contacts/src/test/resources/applicationContext-contacts-test.xml index 39b71cdc66..cf9cce13fe 100644 --- a/samples/contacts/src/test/resources/applicationContext-contacts-test.xml +++ b/samples/contacts/src/test/resources/applicationContext-contacts-test.xml @@ -14,7 +14,7 @@ http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.5.xsd"> - + @@ -24,8 +24,12 @@ + + + + - +