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 afaab55bee..f59346e89b 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -31,14 +31,6 @@ import org.w3c.dom.Element; */ abstract class ConfigUtils { - @SuppressWarnings("unchecked") - static void registerDefaultWebAccessManagerIfNecessary(ParserContext parserContext) { - if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.WEB_ACCESS_MANAGER)) { - parserContext.getRegistry().registerBeanDefinition(BeanIds.WEB_ACCESS_MANAGER, - createAccessManagerBean(RoleVoter.class, AuthenticatedVoter.class)); - } - } - @SuppressWarnings("unchecked") static void registerDefaultMethodAccessManagerIfNecessary(ParserContext parserContext) { if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER)) { @@ -48,7 +40,7 @@ abstract class ConfigUtils { } @SuppressWarnings("unchecked") - private static BeanDefinition createAccessManagerBean(Class... voters) { + static BeanDefinition createAccessManagerBean(Class... voters) { ManagedList defaultVoters = new ManagedList(voters.length); for(Class voter : voters) { 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 9ec92c7ce6..fb7951fc5d 100644 --- a/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java @@ -37,17 +37,18 @@ import org.w3c.dom.Element; * Processes the top-level "global-method-security" element. * * @author Ben Alex + * @author Luke Taylor * @version $Id$ + * @since 2.0 */ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { private final Log logger = LogFactory.getLog(getClass()); - static final String SECURED_DEPENDENCY_CLASS = "org.springframework.security.annotation.Secured"; - static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource"; - static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.method.ExpressionAnnotationMethodDefinitionSource"; - static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource"; - static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter"; + private static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource"; + private static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.method.ExpressionAnnotationMethodDefinitionSource"; + private static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource"; + private static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter"; /* * Internal Bean IDs which are only used within this class @@ -56,7 +57,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String INTERCEPTOR_POST_PROCESSOR_ID = "_globalMethodSecurityInterceptorPostProcessor"; static final String ACCESS_MANAGER_ID = "_globalMethodSecurityAccessManager"; private static final String DELEGATING_METHOD_DEFINITION_SOURCE_ID = "_delegatingMethodDefinitionSource"; - private static final String EXPRESSION_HANDLER_ID = "_expressionHandler"; + private static final String EXPRESSION_HANDLER_ID = "_methodExpressionHandler"; private static final String ATT_ACCESS = "access"; private static final String ATT_EXPRESSION = "expression"; @@ -74,9 +75,33 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { boolean jsr250Enabled = "enabled".equals(element.getAttribute(ATT_USE_JSR250)); boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED)); boolean expressionsEnabled = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS)); + BeanDefinition expressionVoter = null; if (expressionsEnabled) { - delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER); + String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref"); + + if (StringUtils.hasText(expressionHandlerRef)) { + logger.info("Using bean '" + expressionHandlerRef + "' as method SecurityExpressionHandler implementation"); + } else { + parserContext.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, new RootBeanDefinition(DefaultSecurityExpressionHandler.class)); + logger.warn("Expressions were enabled for method security but no SecurityExpressionHandler was configured. " + + "All hasPermision() expressions will evaluate to false."); + expressionHandlerRef = EXPRESSION_HANDLER_ID; + } + BeanDefinitionBuilder expressionVoterBldr = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class); + BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class); + expressionVoterBldr.addPropertyReference("expressionHandler", expressionHandlerRef); + expressionVoter = expressionVoterBldr.getBeanDefinition(); + // After-invocation provider to handle post-invocation filtering and authorization expression annotations. + afterInvocationProvider.addPropertyReference("expressionHandler", expressionHandlerRef); + ConfigUtils.getRegisteredAfterInvocationProviders(parserContext).add(afterInvocationProvider.getBeanDefinition()); + // Add the expression method definition source, which will obtain its parser from the registered expression + // handler + BeanDefinitionBuilder mds = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS); + mds.addConstructorArgReference(expressionHandlerRef); + + delegates.add(mds.getBeanDefinition()); } if (useSecured) { @@ -103,7 +128,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); if (!StringUtils.hasText(accessManagerId)) { - registerAccessManager(element, parserContext, jsr250Enabled, expressionsEnabled); + registerAccessManager(parserContext, jsr250Enabled, expressionVoter); accessManagerId = ACCESS_MANAGER_ID; } @@ -118,35 +143,17 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { /** * Register the default AccessDecisionManager. Adds the special JSR 250 voter jsr-250 is enabled and an - * expression voter if expression-based access control is enabled. If expressions are in use, a after-invocation - * provider will also be registered to handle post-invocation filtering and authorization expression annotations. + * expression voter if expression-based access control is enabled. */ @SuppressWarnings("unchecked") - private void registerAccessManager(Element element, ParserContext pc, boolean jsr250Enabled, boolean expressionsEnabled) { - Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER); + private void registerAccessManager(ParserContext pc, boolean jsr250Enabled, BeanDefinition expressionVoter) { + BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); ManagedList voters = new ManagedList(4); - if (expressionsEnabled) { - BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class); - BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class); - String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref"); - - if (StringUtils.hasText(expressionHandlerRef)) { - logger.info("Using bean '" + expressionHandlerRef + "' as SecurityExpressionHandler implementation"); - } else { - 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; - } - - expressionVoter.addPropertyReference("expressionHandler", expressionHandlerRef); - afterInvocationProvider.addPropertyReference("expressionHandler", expressionHandlerRef); - ConfigUtils.getRegisteredAfterInvocationProviders(pc).add(afterInvocationProvider.getBeanDefinition()); - voters.add(expressionVoter.getBeanDefinition()); + if (expressionVoter != null) { + voters.add(expressionVoter); } - voters.add(new RootBeanDefinition(RoleVoter.class)); voters.add(new RootBeanDefinition(AuthenticatedVoter.class)); 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 24abae69b4..d9661c6b2a 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -1,5 +1,6 @@ package org.springframework.security.config; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -20,6 +21,7 @@ import org.springframework.security.ConfigAttributeEditor; import org.springframework.security.SecurityConfig; import org.springframework.security.context.HttpSessionSecurityContextRepository; import org.springframework.security.context.SecurityContextPersistenceFilter; +import org.springframework.security.expression.web.WebExpressionVoter; import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource; import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.intercept.web.RequestKey; @@ -37,6 +39,9 @@ import org.springframework.security.util.AntUrlPathMatcher; import org.springframework.security.util.FilterChainProxy; import org.springframework.security.util.RegexUrlPathMatcher; import org.springframework.security.util.UrlMatcher; +import org.springframework.security.vote.AccessDecisionVoter; +import org.springframework.security.vote.AuthenticatedVoter; +import org.springframework.security.vote.RoleVoter; import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; @@ -101,6 +106,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref"; + private static final String EXPRESSION_FIDS_CLASS = "org.springframework.security.expression.web.ExpressionBasedFilterInvocationDefinitionSource"; + private static final String EXPRESSION_HANDLER_CLASS = "org.springframework.security.expression.support.DefaultSecurityExpressionHandler"; + private static final String EXPRESSION_HANDLER_ID = "_webExpressionHandler"; + @SuppressWarnings("unchecked") public BeanDefinition parse(Element element, ParserContext parserContext) { ConfigUtils.registerProviderManagerIfNecessary(parserContext); @@ -125,14 +134,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { registerServletApiFilter(element, parserContext); - // Set up the access manager reference for http - String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); - - if (!StringUtils.hasText(accessManagerId)) { - ConfigUtils.registerDefaultWebAccessManagerIfNecessary(parserContext); - accessManagerId = BeanIds.WEB_ACCESS_MANAGER; - } - // Register the portMapper. A default will always be created, even if no element exists. BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse( DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext); @@ -140,7 +141,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { registerExceptionTranslationFilter(element, parserContext, allowSessionCreation); - if (channelRequestMap.size() > 0) { // At least one channel requirement has been specified registerChannelProcessingBeans(parserContext, matcher, channelRequestMap); @@ -148,8 +148,47 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { boolean useExpressions = "true".equals(element.getAttribute(ATT_USE_EXPRESSIONS)); - registerFilterSecurityInterceptor(element, parserContext, matcher, accessManagerId, - parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, useExpressions, parserContext)); + LinkedHashMap> requestToAttributesMap = + parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, useExpressions, parserContext); + + BeanDefinitionBuilder fidsBuilder; + Class[] voters; + + if (useExpressions) { + Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, 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 { + parserContext.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, + BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_HANDLER_CLASS).getBeanDefinition()); + expressionHandlerRef = EXPRESSION_HANDLER_ID; + } + + fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_FIDS_CLASS); + fidsBuilder.addConstructorArgValue(matcher); + fidsBuilder.addConstructorArgValue(requestToAttributesMap); + fidsBuilder.addConstructorArgReference(expressionHandlerRef); + voters = new Class[] {WebExpressionVoter.class}; + } else { + fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationDefinitionSource.class); + fidsBuilder.addConstructorArgValue(matcher); + fidsBuilder.addConstructorArgValue(requestToAttributesMap); + voters = new Class[] {RoleVoter.class, AuthenticatedVoter.class}; + } + fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher); + + // Set up the access manager reference for http + String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); + + if (!StringUtils.hasText(accessManagerId)) { + parserContext.getRegistry().registerBeanDefinition(BeanIds.WEB_ACCESS_MANAGER, + ConfigUtils.createAccessManagerBean(voters)); + accessManagerId = BeanIds.WEB_ACCESS_MANAGER; + } + + registerFilterSecurityInterceptor(element, parserContext, accessManagerId, fidsBuilder.getBeanDefinition()); boolean sessionControlEnabled = registerConcurrentSessionControlBeansIfRequired(element, parserContext); @@ -303,8 +342,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.EXCEPTION_TRANSLATION_FILTER)); } - private void registerFilterSecurityInterceptor(Element element, ParserContext pc, UrlMatcher matcher, - String accessManagerId, LinkedHashMap> filterInvocationDefinitionMap) { + private void registerFilterSecurityInterceptor(Element element, ParserContext pc, String accessManagerId, + BeanDefinition fids) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class); builder.addPropertyReference("accessDecisionManager", accessManagerId); @@ -314,10 +353,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE); } - DefaultFilterInvocationDefinitionSource fids = - new DefaultFilterInvocationDefinitionSource(matcher, filterInvocationDefinitionMap); - fids.setStripQueryStringFromUrls(matcher instanceof AntUrlPathMatcher); - builder.addPropertyValue("objectDefinitionSource", fids); pc.getRegistry().registerBeanDefinition(BeanIds.FILTER_SECURITY_INTERCEPTOR, builder.getBeanDefinition()); ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.FILTER_SECURITY_INTERCEPTOR)); @@ -635,7 +670,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { if (useExpressions) { logger.info("Creating access control expression attribute '" + access + "' for " + key); - + attributes = new ArrayList(1); + // The expression will be parsed later by the ExpressionFilterInvocationDefinitionSource + attributes.add(new SecurityConfig(access)); } else { attributes = SecurityConfig.createList(StringUtils.commaDelimitedListToStringArray(access)); diff --git a/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java index 43c9816e4e..36e1fe25c7 100644 --- a/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java @@ -3,6 +3,7 @@ package org.springframework.security.expression; import org.aopalliance.intercept.MethodInvocation; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; import org.springframework.security.Authentication; import org.springframework.security.intercept.web.FilterInvocation; @@ -15,14 +16,18 @@ import org.springframework.security.intercept.web.FilterInvocation; * @since 2.5 */ public interface SecurityExpressionHandler { + /** + * @return an expression parser for the expressions used by the implementation. + */ + ExpressionParser getExpressionParser(); /** - * Provides a evaluation context in which to evaluate security expressions for a method invocation. + * Provides an evaluation context in which to evaluate security expressions for a method invocation. */ EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation mi); /** - * Provides a evaluation context in which to evaluate security expressions for a web invocation. + * Provides an evaluation context in which to evaluate security expressions for a web invocation. */ EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi); diff --git a/core/src/main/java/org/springframework/security/expression/method/ExpressionAnnotationMethodDefinitionSource.java b/core/src/main/java/org/springframework/security/expression/method/ExpressionAnnotationMethodDefinitionSource.java index 50fe2beaeb..6e04f4c7b5 100644 --- a/core/src/main/java/org/springframework/security/expression/method/ExpressionAnnotationMethodDefinitionSource.java +++ b/core/src/main/java/org/springframework/security/expression/method/ExpressionAnnotationMethodDefinitionSource.java @@ -13,6 +13,7 @@ import org.springframework.expression.ParseException; import org.springframework.expression.spel.SpelExpressionParser; import org.springframework.security.ConfigAttribute; import org.springframework.security.config.SecurityConfigurationException; +import org.springframework.security.expression.SecurityExpressionHandler; import org.springframework.security.expression.annotation.PostAuthorize; import org.springframework.security.expression.annotation.PostFilter; import org.springframework.security.expression.annotation.PreAuthorize; @@ -38,7 +39,19 @@ import org.springframework.util.ClassUtils; * @version $Id$ */ public class ExpressionAnnotationMethodDefinitionSource extends AbstractMethodDefinitionSource { - private ExpressionParser parser = new SpelExpressionParser(); + private ExpressionParser parser; + + public ExpressionAnnotationMethodDefinitionSource() { + parser = new SpelExpressionParser(); + } + + /** + * Constructor which obtains the expression parser from the {@link SecurityExpressionHandler#getExpressionParser() } + * method on the supplied SecurityExpressionHandler. + */ + public ExpressionAnnotationMethodDefinitionSource(SecurityExpressionHandler handler) { + parser = handler.getExpressionParser(); + } public List getAttributes(Method method, Class targetClass) { if (method.getDeclaringClass() == Object.class) { @@ -67,7 +80,7 @@ public class ExpressionAnnotationMethodDefinitionSource extends AbstractMethodDe * for the logic of this method. The ordering here is slightly different in that we consider method-specific * annotations on an interface before class-level ones. */ - private A findAnnotation(Method method, Class targetClass, Class annotationClass) { + private A findAnnotation(Method method, Class targetClass, Class annotationClass) { // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); diff --git a/core/src/main/java/org/springframework/security/expression/support/DefaultSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/expression/support/DefaultSecurityExpressionHandler.java index aa7e1c619a..712cee2b6f 100644 --- a/core/src/main/java/org/springframework/security/expression/support/DefaultSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/expression/support/DefaultSecurityExpressionHandler.java @@ -12,6 +12,8 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelExpressionParser; import org.springframework.expression.spel.standard.StandardEvaluationContext; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationTrustResolver; @@ -24,7 +26,7 @@ import org.springframework.security.intercept.web.FilterInvocation; /** * The standard implementation of SecurityExpressionHandler. *

- * A single instance should usually be shared. + * A single instance should usually be shared amongst the beans that require expression support. * * @author Luke Taylor * @version $Id$ @@ -37,6 +39,7 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private ExpressionParser expressionParser = new SpelExpressionParser(); public DefaultSecurityExpressionHandler() { } @@ -57,6 +60,8 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) { StandardEvaluationContext ctx = new StandardEvaluationContext(); + SecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi); + ctx.setRootObject(root); return ctx; } @@ -127,6 +132,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl throw new IllegalArgumentException("Filter target must be a collection or array type, but was " + filterTarget); } + public ExpressionParser getExpressionParser() { + return expressionParser; + } + public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; } diff --git a/core/src/main/java/org/springframework/security/expression/support/MethodInvocationSecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/expression/support/MethodInvocationSecurityExpressionRoot.java deleted file mode 100644 index 74173ae47b..0000000000 --- a/core/src/main/java/org/springframework/security/expression/support/MethodInvocationSecurityExpressionRoot.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.springframework.security.expression.support; - -import java.io.Serializable; - -import org.springframework.security.Authentication; -import org.springframework.security.expression.PermissionEvaluator; - -public class MethodInvocationSecurityExpressionRoot extends SecurityExpressionRoot { - private PermissionEvaluator permissionEvaluator; - private Object filterObject; - private Object returnObject; - public final String read = "read"; - public final String write = "write"; - public final String create = "create"; - public final String delete = "delete"; - public final String admin = "administration"; - - MethodInvocationSecurityExpressionRoot(Authentication a) { - super(a); - } - - public boolean hasPermission(Object target, Object permission) { - return permissionEvaluator.hasPermission(authentication, target, permission); - } - - public boolean hasPermission(Object targetId, String targetType, Object permission) { - return permissionEvaluator.hasPermission(authentication, (Serializable)targetId, targetType, permission); - } - - public void setFilterObject(Object filterObject) { - this.filterObject = filterObject; - } - - public Object getFilterObject() { - return filterObject; - } - - public void setReturnObject(Object returnObject) { - this.returnObject = returnObject; - } - - public Object getReturnObject() { - return returnObject; - } - - public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) { - this.permissionEvaluator = permissionEvaluator; - } - -} diff --git a/core/src/main/java/org/springframework/security/expression/support/WebSecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/expression/support/WebSecurityExpressionRoot.java new file mode 100644 index 0000000000..18bf814fb1 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/WebSecurityExpressionRoot.java @@ -0,0 +1,19 @@ +package org.springframework.security.expression.support; + +import org.springframework.security.Authentication; +import org.springframework.security.intercept.web.FilterInvocation; + +/** + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +class WebSecurityExpressionRoot extends SecurityExpressionRoot { + private FilterInvocation filterInvocation; + + WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) { + super(a); + this.filterInvocation = fi; + } +} diff --git a/core/src/main/java/org/springframework/security/expression/web/ExpressionBasedFilterInvocationDefinitionSource.java b/core/src/main/java/org/springframework/security/expression/web/ExpressionBasedFilterInvocationDefinitionSource.java new file mode 100644 index 0000000000..7da24cb281 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/web/ExpressionBasedFilterInvocationDefinitionSource.java @@ -0,0 +1,60 @@ +package org.springframework.security.expression.web; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParseException; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.expression.SecurityExpressionHandler; +import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource; +import org.springframework.security.intercept.web.RequestKey; +import org.springframework.security.util.UrlMatcher; +import org.springframework.util.Assert; + +/** + * Expression-based FilterInvocationDefinitionSource. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +public final class ExpressionBasedFilterInvocationDefinitionSource extends DefaultFilterInvocationDefinitionSource { + private final static Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationDefinitionSource.class); + + public ExpressionBasedFilterInvocationDefinitionSource(UrlMatcher urlMatcher, + LinkedHashMap> requestMap, SecurityExpressionHandler expressionHandler) { + super(urlMatcher, processMap(requestMap, expressionHandler.getExpressionParser())); + Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required"); + } + + private static LinkedHashMap> processMap( + LinkedHashMap> requestMap, ExpressionParser parser) { + Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object"); + + LinkedHashMap> requestToExpressionAttributesMap = + new LinkedHashMap>(requestMap); + + for (Map.Entry> entry : requestMap.entrySet()) { + RequestKey request = entry.getKey(); + Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request); + ArrayList attributes = new ArrayList(1); + String expression = entry.getValue().get(0).getAttribute(); + logger.debug("Adding web access control expression '" + expression + "', for " + request); + try { + attributes.add(new WebExpressionConfigAttribute(parser.parseExpression(expression))); + } catch (ParseException e) { + throw new IllegalArgumentException("Failed to parse expression '" + expression + "'"); + } + + requestToExpressionAttributesMap.put(request, attributes); + } + + return requestToExpressionAttributesMap; + } + +} diff --git a/core/src/main/java/org/springframework/security/expression/web/WebExpressionConfigAttribute.java b/core/src/main/java/org/springframework/security/expression/web/WebExpressionConfigAttribute.java index 103027340b..e7ad2d603f 100644 --- a/core/src/main/java/org/springframework/security/expression/web/WebExpressionConfigAttribute.java +++ b/core/src/main/java/org/springframework/security/expression/web/WebExpressionConfigAttribute.java @@ -1,8 +1,6 @@ package org.springframework.security.expression.web; import org.springframework.expression.Expression; -import org.springframework.expression.ParseException; -import org.springframework.expression.spel.SpelExpressionParser; import org.springframework.security.ConfigAttribute; /** @@ -15,10 +13,6 @@ import org.springframework.security.ConfigAttribute; class WebExpressionConfigAttribute implements ConfigAttribute { private final Expression authorizeExpression; - public WebExpressionConfigAttribute(String authorizeExpression) throws ParseException { - this.authorizeExpression = new SpelExpressionParser().parseExpression(authorizeExpression); - } - public WebExpressionConfigAttribute(Expression authorizeExpression) { this.authorizeExpression = authorizeExpression; } diff --git a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java index b409a4e8f0..5348db328b 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -1,11 +1,6 @@ package org.springframework.security.config; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML; import java.lang.reflect.Method; @@ -19,21 +14,24 @@ import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.support.AbstractXmlApplicationContext; +import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.AccessDeniedException; import org.springframework.security.ConfigAttribute; import org.springframework.security.MockAuthenticationEntryPoint; -import org.springframework.security.MockFilterChain; import org.springframework.security.SecurityConfig; import org.springframework.security.concurrent.ConcurrentLoginException; import org.springframework.security.concurrent.ConcurrentSessionControllerImpl; import org.springframework.security.concurrent.ConcurrentSessionFilter; import org.springframework.security.context.HttpSessionSecurityContextRepository; +import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.context.SecurityContextPersistenceFilter; import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.security.intercept.web.FilterInvocationDefinitionSource; import org.springframework.security.intercept.web.FilterSecurityInterceptor; +import org.springframework.security.providers.TestingAuthenticationToken; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.anonymous.AnonymousProcessingFilter; import org.springframework.security.securechannel.ChannelProcessingFilter; @@ -69,6 +67,7 @@ public class HttpSecurityBeanDefinitionParserTests { appContext.close(); appContext = null; } + SecurityContextHolder.clearContext(); } @Test @@ -691,18 +690,38 @@ public class HttpSecurityBeanDefinitionParserTests { } @Test - public void createDefinedSecurityContextRepository() throws Exception { + public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception { setContext( - "" + - "" + - " " + - "" + AUTH_PROVIDER_XML); + " " + + " " + + " " + + " " + AUTH_PROVIDER_XML); + + FilterSecurityInterceptor fis = (FilterSecurityInterceptor) appContext.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR); + + FilterInvocationDefinitionSource fids = fis.getObjectDefinitionSource(); + List attrDef = fids.getAttributes(createFilterinvocation("/secure", null)); + assertEquals(1, attrDef.size()); + + // Try an unprotected invocation + SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_A")); + fis.invoke(createFilterinvocation("/permitallurl", null)); + // Try secure Url as a valid user + fis.invoke(createFilterinvocation("/securex", null)); + // And as a user without the required role + SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_B")); + try { + fis.invoke(createFilterinvocation("/securex", null)); + fail("Expected AccessDeniedInvocation"); + } catch (AccessDeniedException expected) { + } } private void setContext(String context) { appContext = new InMemoryXmlApplicationContext(context); } + @SuppressWarnings("unchecked") private List getFilters(String url) throws Exception { FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY); Method getFilters = fcp.getClass().getDeclaredMethod("getFilters", String.class);