diff --git a/config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java b/config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java index 7e4e48bbd9..3dc5c27f3d 100644 --- a/config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java +++ b/config/src/main/java/org/springframework/security/config/http/FilterInvocationSecurityMetadataSourceParser.java @@ -8,6 +8,7 @@ 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.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; @@ -59,7 +60,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit return mds; } - static BeanDefinition createSecurityMetadataSource(List interceptUrls, Element elt, ParserContext pc) { + static RootBeanDefinition createSecurityMetadataSource(List interceptUrls, Element elt, ParserContext pc) { MatcherType matcherType = MatcherType.fromElement(elt); boolean useExpressions = isUseExpressions(elt); @@ -87,7 +88,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt)); - return fidsBuilder.getBeanDefinition(); + return (RootBeanDefinition) fidsBuilder.getBeanDefinition(); } static String registerDefaultExpressionHandler(ParserContext pc) { diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 74d9f189e1..01731fed9b 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -451,15 +451,17 @@ class HttpConfigurationBuilder { private void createFilterSecurityInterceptor(BeanReference authManager) { boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt); - BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc); + RootBeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc); RootBeanDefinition accessDecisionMgr; ManagedList voters = new ManagedList(2); if (useExpressions) { BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(WebExpressionVoter.class); - RuntimeBeanReference expressionHandler = new RuntimeBeanReference( - FilterInvocationSecurityMetadataSourceParser.registerDefaultExpressionHandler(pc)); + // Read the expression handler from the FISMS + RuntimeBeanReference expressionHandler = (RuntimeBeanReference) + securityMds.getConstructorArgumentValues().getArgumentValue(1, RuntimeBeanReference.class).getValue(); + expressionVoter.addPropertyValue("expressionHandler", expressionHandler); voters.add(expressionVoter.getBeanDefinition()); diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc index dda2a172a5..c32a1c1033 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc @@ -202,7 +202,7 @@ msmds.attlist &= use-expressions? 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, (pre-post-annotation-handling | expression-handler)?, protect-pointcut*, after-invocation-provider*} + element global-method-security {global-method-security.attlist, (pre-post-annotation-handling | expression-handler)?, protect-pointcut*, after-invocation-provider*} global-method-security.attlist &= ## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "disabled". attribute pre-post-annotations {"disabled" | "enabled" }? @@ -268,7 +268,7 @@ http-firewall = http = ## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "secured" attribute to "false". - element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache?) } + element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler?) } http.attlist &= ## The request URL pattern which will be mapped to the filter chain created by this element. If omitted, the filter chain will match all requests. attribute pattern {xsd:token}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd index f1354fa1c1..53245401f6 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd @@ -682,6 +682,11 @@ + + 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. + + + diff --git a/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy index b25c59adac..001f525e9a 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy @@ -50,6 +50,9 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider import org.springframework.security.access.vote.RoleVoter import org.springframework.security.web.access.expression.WebExpressionVoter import org.springframework.security.access.vote.AffirmativeBased +import org.springframework.security.access.PermissionEvaluator +import org.springframework.security.core.Authentication +import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler class MiscHttpConfigTests extends AbstractHttpConfigTests { def 'Minimal configuration parses'() { @@ -497,6 +500,25 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests { thrown(AccessDeniedException) } + def expressionBasedAccessSupportsExternalExpressionHandler() { + setup: + xml.http('auto-config': 'true', 'use-expressions': 'true') { + interceptUrl('/**', "hasPermission('AnyObject','R')") + 'expression-handler'(ref: 'expressionHandler') + } + bean('expressionHandler', DefaultWebSecurityExpressionHandler.class.name, [:], [permissionEvaluator: 'pe']) + bean('pe', MockPermissionEvaluator) + createAppContext() + + def fis = getFilter(FilterSecurityInterceptor) + + when: "Invoking allowed URL protected by hasPermission() expression succeeds" + SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ANY")); + fis.invoke(createFilterinvocation("/secure", null)); + then: + notThrown(AccessDeniedException) + } + def protectedLoginPageReportsWarning() { when: xml.http('use-expressions': 'true') { @@ -647,6 +669,17 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests { } } +class MockPermissionEvaluator implements PermissionEvaluator { + boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + return true + } + + boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { + return true + } + +} + class MockEntryPoint extends LoginUrlAuthenticationEntryPoint { public MockEntryPoint() { super.setLoginFormUrl("/notused");