SEC-1033: Completed working version of web expression support.

SEC-999: Added getExpressionParser() method to the security handler interface to allow both web and method expression security to obtain a suitable parser from the configuration for parsing their expression attributes.
This commit is contained in:
Luke Taylor 2008-12-08 01:01:14 +00:00
parent fd3990c1f8
commit 6b4045667a
11 changed files with 236 additions and 131 deletions

View File

@ -31,14 +31,6 @@ import org.w3c.dom.Element;
*/ */
abstract class ConfigUtils { 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") @SuppressWarnings("unchecked")
static void registerDefaultMethodAccessManagerIfNecessary(ParserContext parserContext) { static void registerDefaultMethodAccessManagerIfNecessary(ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER)) { if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER)) {
@ -48,7 +40,7 @@ abstract class ConfigUtils {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static BeanDefinition createAccessManagerBean(Class<? extends AccessDecisionVoter>... voters) { static BeanDefinition createAccessManagerBean(Class<? extends AccessDecisionVoter>... voters) {
ManagedList defaultVoters = new ManagedList(voters.length); ManagedList defaultVoters = new ManagedList(voters.length);
for(Class<? extends AccessDecisionVoter> voter : voters) { for(Class<? extends AccessDecisionVoter> voter : voters) {

View File

@ -37,17 +37,18 @@ import org.w3c.dom.Element;
* Processes the top-level "global-method-security" element. * Processes the top-level "global-method-security" element.
* *
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor
* @version $Id$ * @version $Id$
* @since 2.0
*/ */
class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
static final String SECURED_DEPENDENCY_CLASS = "org.springframework.security.annotation.Secured"; private static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource";
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";
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";
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";
static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter";
/* /*
* Internal Bean IDs which are only used within this class * 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 INTERCEPTOR_POST_PROCESSOR_ID = "_globalMethodSecurityInterceptorPostProcessor";
static final String ACCESS_MANAGER_ID = "_globalMethodSecurityAccessManager"; static final String ACCESS_MANAGER_ID = "_globalMethodSecurityAccessManager";
private static final String DELEGATING_METHOD_DEFINITION_SOURCE_ID = "_delegatingMethodDefinitionSource"; 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_ACCESS = "access";
private static final String ATT_EXPRESSION = "expression"; 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 jsr250Enabled = "enabled".equals(element.getAttribute(ATT_USE_JSR250));
boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED)); boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED));
boolean expressionsEnabled = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS)); boolean expressionsEnabled = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS));
BeanDefinition expressionVoter = null;
if (expressionsEnabled) { 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) { if (useSecured) {
@ -103,7 +128,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
if (!StringUtils.hasText(accessManagerId)) { if (!StringUtils.hasText(accessManagerId)) {
registerAccessManager(element, parserContext, jsr250Enabled, expressionsEnabled); registerAccessManager(parserContext, jsr250Enabled, expressionVoter);
accessManagerId = ACCESS_MANAGER_ID; 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 * 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 * expression voter if expression-based access control is enabled.
* provider will also be registered to handle post-invocation filtering and authorization expression annotations.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void registerAccessManager(Element element, ParserContext pc, boolean jsr250Enabled, boolean expressionsEnabled) { private void registerAccessManager(ParserContext pc, boolean jsr250Enabled, BeanDefinition expressionVoter) {
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class);
ManagedList voters = new ManagedList(4); ManagedList voters = new ManagedList(4);
if (expressionsEnabled) { if (expressionVoter != null) {
BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class); voters.add(expressionVoter);
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());
}
voters.add(new RootBeanDefinition(RoleVoter.class)); voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class)); voters.add(new RootBeanDefinition(AuthenticatedVoter.class));

View File

@ -1,5 +1,6 @@
package org.springframework.security.config; package org.springframework.security.config;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -20,6 +21,7 @@ import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.SecurityConfig; import org.springframework.security.SecurityConfig;
import org.springframework.security.context.HttpSessionSecurityContextRepository; import org.springframework.security.context.HttpSessionSecurityContextRepository;
import org.springframework.security.context.SecurityContextPersistenceFilter; 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.DefaultFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.intercept.web.RequestKey; 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.FilterChainProxy;
import org.springframework.security.util.RegexUrlPathMatcher; import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.security.util.UrlMatcher; 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.security.wrapper.SecurityContextHolderAwareRequestFilter;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils; 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 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") @SuppressWarnings("unchecked")
public BeanDefinition parse(Element element, ParserContext parserContext) { public BeanDefinition parse(Element element, ParserContext parserContext) {
ConfigUtils.registerProviderManagerIfNecessary(parserContext); ConfigUtils.registerProviderManagerIfNecessary(parserContext);
@ -125,14 +134,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
registerServletApiFilter(element, parserContext); 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. // Register the portMapper. A default will always be created, even if no element exists.
BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse( BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse(
DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext); DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext);
@ -140,7 +141,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
registerExceptionTranslationFilter(element, parserContext, allowSessionCreation); registerExceptionTranslationFilter(element, parserContext, allowSessionCreation);
if (channelRequestMap.size() > 0) { if (channelRequestMap.size() > 0) {
// At least one channel requirement has been specified // At least one channel requirement has been specified
registerChannelProcessingBeans(parserContext, matcher, channelRequestMap); registerChannelProcessingBeans(parserContext, matcher, channelRequestMap);
@ -148,8 +148,47 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
boolean useExpressions = "true".equals(element.getAttribute(ATT_USE_EXPRESSIONS)); boolean useExpressions = "true".equals(element.getAttribute(ATT_USE_EXPRESSIONS));
registerFilterSecurityInterceptor(element, parserContext, matcher, accessManagerId, LinkedHashMap<RequestKey, List<ConfigAttribute>> requestToAttributesMap =
parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, useExpressions, parserContext)); parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, useExpressions, parserContext);
BeanDefinitionBuilder fidsBuilder;
Class<? extends AccessDecisionVoter>[] 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); boolean sessionControlEnabled = registerConcurrentSessionControlBeansIfRequired(element, parserContext);
@ -303,8 +342,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.EXCEPTION_TRANSLATION_FILTER)); ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.EXCEPTION_TRANSLATION_FILTER));
} }
private void registerFilterSecurityInterceptor(Element element, ParserContext pc, UrlMatcher matcher, private void registerFilterSecurityInterceptor(Element element, ParserContext pc, String accessManagerId,
String accessManagerId, LinkedHashMap<RequestKey, List<ConfigAttribute>> filterInvocationDefinitionMap) { BeanDefinition fids) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
builder.addPropertyReference("accessDecisionManager", accessManagerId); builder.addPropertyReference("accessDecisionManager", accessManagerId);
@ -314,10 +353,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE); builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
} }
DefaultFilterInvocationDefinitionSource fids =
new DefaultFilterInvocationDefinitionSource(matcher, filterInvocationDefinitionMap);
fids.setStripQueryStringFromUrls(matcher instanceof AntUrlPathMatcher);
builder.addPropertyValue("objectDefinitionSource", fids); builder.addPropertyValue("objectDefinitionSource", fids);
pc.getRegistry().registerBeanDefinition(BeanIds.FILTER_SECURITY_INTERCEPTOR, builder.getBeanDefinition()); pc.getRegistry().registerBeanDefinition(BeanIds.FILTER_SECURITY_INTERCEPTOR, builder.getBeanDefinition());
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.FILTER_SECURITY_INTERCEPTOR)); ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.FILTER_SECURITY_INTERCEPTOR));
@ -635,7 +670,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (useExpressions) { if (useExpressions) {
logger.info("Creating access control expression attribute '" + access + "' for " + key); logger.info("Creating access control expression attribute '" + access + "' for " + key);
attributes = new ArrayList<ConfigAttribute>(1);
// The expression will be parsed later by the ExpressionFilterInvocationDefinitionSource
attributes.add(new SecurityConfig(access));
} else { } else {
attributes = SecurityConfig.createList(StringUtils.commaDelimitedListToStringArray(access)); attributes = SecurityConfig.createList(StringUtils.commaDelimitedListToStringArray(access));

View File

@ -3,6 +3,7 @@ package org.springframework.security.expression;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.Authentication; import org.springframework.security.Authentication;
import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.security.intercept.web.FilterInvocation;
@ -15,14 +16,18 @@ import org.springframework.security.intercept.web.FilterInvocation;
* @since 2.5 * @since 2.5
*/ */
public interface SecurityExpressionHandler { 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); 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); EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi);

View File

@ -13,6 +13,7 @@ import org.springframework.expression.ParseException;
import org.springframework.expression.spel.SpelExpressionParser; import org.springframework.expression.spel.SpelExpressionParser;
import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttribute;
import org.springframework.security.config.SecurityConfigurationException; 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.PostAuthorize;
import org.springframework.security.expression.annotation.PostFilter; import org.springframework.security.expression.annotation.PostFilter;
import org.springframework.security.expression.annotation.PreAuthorize; import org.springframework.security.expression.annotation.PreAuthorize;
@ -38,7 +39,19 @@ import org.springframework.util.ClassUtils;
* @version $Id$ * @version $Id$
*/ */
public class ExpressionAnnotationMethodDefinitionSource extends AbstractMethodDefinitionSource { 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 <tt>SecurityExpressionHandler</tt>.
*/
public ExpressionAnnotationMethodDefinitionSource(SecurityExpressionHandler handler) {
parser = handler.getExpressionParser();
}
public List<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) { public List<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) { 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 * 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. * annotations on an interface before class-level ones.
*/ */
private <A extends Annotation> A findAnnotation(Method method, Class targetClass, Class<A> annotationClass) { private <A extends Annotation> A findAnnotation(Method method, Class<?> targetClass, Class<A> annotationClass) {
// The method may be on an interface, but we need attributes from the target class. // 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. // If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);

View File

@ -12,6 +12,8 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; 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.expression.spel.standard.StandardEvaluationContext;
import org.springframework.security.Authentication; import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationTrustResolver; import org.springframework.security.AuthenticationTrustResolver;
@ -24,7 +26,7 @@ import org.springframework.security.intercept.web.FilterInvocation;
/** /**
* The standard implementation of <tt>SecurityExpressionHandler</tt>. * The standard implementation of <tt>SecurityExpressionHandler</tt>.
* <p> * <p>
* A single instance should usually be shared. * A single instance should usually be shared amongst the beans that require expression support.
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
@ -37,6 +39,7 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private ExpressionParser expressionParser = new SpelExpressionParser();
public DefaultSecurityExpressionHandler() { public DefaultSecurityExpressionHandler() {
} }
@ -57,6 +60,8 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) { public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) {
StandardEvaluationContext ctx = new StandardEvaluationContext(); StandardEvaluationContext ctx = new StandardEvaluationContext();
SecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
ctx.setRootObject(root);
return ctx; 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); 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) { public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer; this.parameterNameDiscoverer = parameterNameDiscoverer;
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 <tt>FilterInvocationDefinitionSource</tt>.
*
* @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<RequestKey, List<ConfigAttribute>> requestMap, SecurityExpressionHandler expressionHandler) {
super(urlMatcher, processMap(requestMap, expressionHandler.getExpressionParser()));
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
}
private static LinkedHashMap<RequestKey, List<ConfigAttribute>> processMap(
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap, ExpressionParser parser) {
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestToExpressionAttributesMap =
new LinkedHashMap<RequestKey, List<ConfigAttribute>>(requestMap);
for (Map.Entry<RequestKey, List<ConfigAttribute>> entry : requestMap.entrySet()) {
RequestKey request = entry.getKey();
Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request);
ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(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;
}
}

View File

@ -1,8 +1,6 @@
package org.springframework.security.expression.web; package org.springframework.security.expression.web;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.SpelExpressionParser;
import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttribute;
/** /**
@ -15,10 +13,6 @@ import org.springframework.security.ConfigAttribute;
class WebExpressionConfigAttribute implements ConfigAttribute { class WebExpressionConfigAttribute implements ConfigAttribute {
private final Expression authorizeExpression; private final Expression authorizeExpression;
public WebExpressionConfigAttribute(String authorizeExpression) throws ParseException {
this.authorizeExpression = new SpelExpressionParser().parseExpression(authorizeExpression);
}
public WebExpressionConfigAttribute(Expression authorizeExpression) { public WebExpressionConfigAttribute(Expression authorizeExpression) {
this.authorizeExpression = authorizeExpression; this.authorizeExpression = authorizeExpression;
} }

View File

@ -1,11 +1,6 @@
package org.springframework.security.config; package org.springframework.security.config;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
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.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML; import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -19,21 +14,24 @@ import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttribute;
import org.springframework.security.MockAuthenticationEntryPoint; import org.springframework.security.MockAuthenticationEntryPoint;
import org.springframework.security.MockFilterChain;
import org.springframework.security.SecurityConfig; import org.springframework.security.SecurityConfig;
import org.springframework.security.concurrent.ConcurrentLoginException; import org.springframework.security.concurrent.ConcurrentLoginException;
import org.springframework.security.concurrent.ConcurrentSessionControllerImpl; import org.springframework.security.concurrent.ConcurrentSessionControllerImpl;
import org.springframework.security.concurrent.ConcurrentSessionFilter; import org.springframework.security.concurrent.ConcurrentSessionFilter;
import org.springframework.security.context.HttpSessionSecurityContextRepository; import org.springframework.security.context.HttpSessionSecurityContextRepository;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.context.SecurityContextPersistenceFilter; import org.springframework.security.context.SecurityContextPersistenceFilter;
import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource; import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.anonymous.AnonymousProcessingFilter; import org.springframework.security.providers.anonymous.AnonymousProcessingFilter;
import org.springframework.security.securechannel.ChannelProcessingFilter; import org.springframework.security.securechannel.ChannelProcessingFilter;
@ -69,6 +67,7 @@ public class HttpSecurityBeanDefinitionParserTests {
appContext.close(); appContext.close();
appContext = null; appContext = null;
} }
SecurityContextHolder.clearContext();
} }
@Test @Test
@ -691,18 +690,38 @@ public class HttpSecurityBeanDefinitionParserTests {
} }
@Test @Test
public void createDefinedSecurityContextRepository() throws Exception { public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception {
setContext( setContext(
"<b:bean id='repo' class='org.springframework.security.context.HttpSessionSecurityContextRepository'/>" + " <http auto-config='true' use-expressions='true'>" +
"<http security-context-repository-ref='repo'>" + " <intercept-url pattern='/secure*' access=\"hasRole('ROLE_A')\" />" +
" <http-basic />" + " <intercept-url pattern='/**' access='permitAll()' />" +
"</http>" + AUTH_PROVIDER_XML); " </http>" + AUTH_PROVIDER_XML);
FilterSecurityInterceptor fis = (FilterSecurityInterceptor) appContext.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR);
FilterInvocationDefinitionSource fids = fis.getObjectDefinitionSource();
List<? extends ConfigAttribute> 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) { private void setContext(String context) {
appContext = new InMemoryXmlApplicationContext(context); appContext = new InMemoryXmlApplicationContext(context);
} }
@SuppressWarnings("unchecked")
private List<Filter> getFilters(String url) throws Exception { private List<Filter> getFilters(String url) throws Exception {
FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY); FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
Method getFilters = fcp.getClass().getDeclaredMethod("getFilters", String.class); Method getFilters = fcp.getClass().getDeclaredMethod("getFilters", String.class);