1
0
mirror of synced 2026-08-04 01:07:02 +00:00

SEC-1023: Add hasPermission() support to SecurityExpressionRoot

http://jira.springframework.org/browse/SEC-1023.

hasPermission() now delegates to a PermissionEvaluator interface, with a default implementation provided by the Acl module. The contacts sample now uses expressions on the ContactManager interface. The permission-evaluator element on global-method-security can be used to set the instance to an AclPermissionEvaluator. If not set, all hasPermission() expressions will evaluate to 'false'.
This commit is contained in:
Luke Taylor
2008-11-10 04:27:25 +00:00
parent fa6f57e3dd
commit e11114ce77
34 changed files with 392 additions and 357 deletions
@@ -1,9 +1,9 @@
package org.springframework.security.config;
/**
* Contains all the default Bean IDs created by the namespace support in Spring Security 2.
* Contains globally used default Bean IDs for beans created by the namespace support in Spring Security 2.
* <p>
* These are mainly intended for internal use.
* These are intended for internal use.
*
* @author Ben Alex
* @version $Id$
@@ -31,7 +31,7 @@ public abstract class BeanIds {
public static final String SESSION_REGISTRY = "_sessionRegistry";
public static final String CONCURRENT_SESSION_FILTER = "_concurrentSessionFilter";
public static final String CONCURRENT_SESSION_CONTROLLER = "_concurrentSessionController";
public static final String METHOD_ACCESS_MANAGER = "_methodAccessManager";
public static final String METHOD_ACCESS_MANAGER = "_defaultMethodAccessManager";
public static final String WEB_ACCESS_MANAGER = "_webAccessManager";
public static final String AUTHENTICATION_MANAGER = "_authenticationManager";
public static final String AFTER_INVOCATION_MANAGER = "_afterInvocationManager";
@@ -55,11 +55,11 @@ public abstract class BeanIds {
public static final String DEFAULT_LOGIN_PAGE_GENERATING_FILTER = "_defaultLoginPageFilter";
public static final String SECURITY_CONTEXT_HOLDER_AWARE_REQUEST_FILTER = "_securityContextHolderAwareRequestFilter";
public static final String SESSION_FIXATION_PROTECTION_FILTER = "_sessionFixationProtectionFilter";
public static final String METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor";
public static final String METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR = "_methodSecurityInterceptorPostProcessor";
// public static final String GLOBAL_METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor";
// public static final String METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR = "_methodSecurityInterceptorPostProcessor";
public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor";
public static final String PROTECT_POINTCUT_POST_PROCESSOR = "_protectPointcutPostProcessor";
public static final String DELEGATING_METHOD_DEFINITION_SOURCE = "_delegatingMethodDefinitionSource";
// public static final String DELEGATING_METHOD_DEFINITION_SOURCE = "_delegatingMethodDefinitionSource";
public static final String SECURED_METHOD_DEFINITION_SOURCE = "_securedMethodDefinitionSource";
public static final String JSR_250_METHOD_DEFINITION_SOURCE = "_jsr250MethodDefinitionSource";
public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer";
@@ -68,17 +68,6 @@ abstract class ConfigUtils {
return nonNulls;
}
static void addVoter(BeanDefinition voter, ParserContext parserContext) {
registerDefaultMethodAccessManagerIfNecessary(parserContext);
BeanDefinition accessMgr = parserContext.getRegistry().getBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER);
ManagedList voters = (ManagedList) accessMgr.getPropertyValues().getPropertyValue("decisionVoters").getValue();
voters.add(voter);
accessMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);
}
/**
* Creates and registers the bean definition for the default ProviderManager instance and returns
* the BeanDefinition for it. This method will typically be called when registering authentication providers
@@ -10,34 +10,35 @@ abstract class Elements {
public static final String AUTHENTICATION_MANAGER = "authentication-manager";
public static final String USER_SERVICE = "user-service";
public static final String JDBC_USER_SERVICE = "jdbc-user-service";
public static final String FILTER_CHAIN_MAP = "filter-chain-map";
public static final String INTERCEPT_METHODS = "intercept-methods";
public static final String INTERCEPT_URL = "intercept-url";
public static final String AUTHENTICATION_PROVIDER = "authentication-provider";
public static final String HTTP = "http";
public static final String LDAP_PROVIDER = "ldap-authentication-provider";
public static final String LDAP_SERVER = "ldap-server";
public static final String JDBC_USER_SERVICE = "jdbc-user-service";
public static final String FILTER_CHAIN_MAP = "filter-chain-map";
public static final String INTERCEPT_METHODS = "intercept-methods";
public static final String INTERCEPT_URL = "intercept-url";
public static final String AUTHENTICATION_PROVIDER = "authentication-provider";
public static final String HTTP = "http";
public static final String LDAP_PROVIDER = "ldap-authentication-provider";
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 PROTECT = "protect";
public static final String CONCURRENT_SESSIONS = "concurrent-session-control";
public static final String LOGOUT = "logout";
public static final String FORM_LOGIN = "form-login";
public static final String OPENID_LOGIN = "openid-login";
public static final String BASIC_AUTH = "http-basic";
public static final String REMEMBER_ME = "remember-me";
public static final String ANONYMOUS = "anonymous";
public static final String FILTER_CHAIN = "filter-chain";
public static final String GLOBAL_METHOD_SECURITY = "global-method-security";
public static final String PASSWORD_ENCODER = "password-encoder";
public static final String SALT_SOURCE = "salt-source";
public static final String PORT_MAPPINGS = "port-mappings";
public static final String CONCURRENT_SESSIONS = "concurrent-session-control";
public static final String LOGOUT = "logout";
public static final String FORM_LOGIN = "form-login";
public static final String OPENID_LOGIN = "openid-login";
public static final String BASIC_AUTH = "http-basic";
public static final String REMEMBER_ME = "remember-me";
public static final String ANONYMOUS = "anonymous";
public static final String FILTER_CHAIN = "filter-chain";
public static final String GLOBAL_METHOD_SECURITY = "global-method-security";
public static final String PASSWORD_ENCODER = "password-encoder";
public static final String SALT_SOURCE = "salt-source";
public static final String PORT_MAPPINGS = "port-mappings";
public static final String PORT_MAPPING = "port-mapping";
public static final String CUSTOM_FILTER = "custom-filter";
public static final String CUSTOM_AUTH_PROVIDER = "custom-authentication-provider";
public static final String CUSTOM_AFTER_INVOCATION_PROVIDER = "custom-after-invocation-provider";
public static final String CUSTOM_AFTER_INVOCATION_PROVIDER = "custom-after-invocation-provider";
public static final String X509 = "x509";
public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source";
public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source";
public static final String LDAP_PASSWORD_COMPARE = "password-compare";
}
@@ -6,6 +6,8 @@ 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.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -17,12 +19,17 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.SecurityConfig;
import org.springframework.security.expression.DefaultSecurityExpressionHandler;
import org.springframework.security.expression.support.MethodExpressionAfterInvocationProvider;
import org.springframework.security.expression.support.MethodExpressionVoter;
import org.springframework.security.intercept.method.DelegatingMethodDefinitionSource;
import org.springframework.security.intercept.method.MapBasedMethodDefinitionSource;
import org.springframework.security.intercept.method.ProtectPointcutPostProcessor;
import org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor;
import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.vote.AffirmativeBased;
import org.springframework.security.vote.AuthenticatedVoter;
import org.springframework.security.vote.RoleVoter;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -34,24 +41,51 @@ import org.w3c.dom.Element;
* @version $Id$
*/
class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
public static final String SECURED_DEPENDENCY_CLASS = "org.springframework.security.annotation.Secured";
public static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource";
public static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.support.ExpressionAnnotationMethodDefinitionSource";
public static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource";
public static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter";
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.support.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";
/*
* Internal Bean IDs which are only used within this class
*/
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 ATT_ACCESS = "access";
private static final String ATT_EXPRESSION = "expression";
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
private static final String ATT_USE_JSR250 = "jsr250-annotations";
private static final String ATT_USE_SECURED = "secured-annotations";
private static final String ATT_USE_EXPRESSIONS = "spel-annotations";
private static final String ATT_USE_EXPRESSIONS = "expression-annotations";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
// The list of method metadata delegates
ManagedList delegates = new ManagedList();
boolean jsr250Enabled = registerAnnotationBasedMethodDefinitionSources(element, parserContext, delegates);
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));
if (expressionsEnabled) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
}
if (useSecured) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(SECURED_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
}
if (jsr250Enabled) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
}
MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource = new MapBasedMethodDefinitionSource();
delegates.add(mapBasedMethodDefinitionSource);
@@ -66,21 +100,11 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
registerDelegatingMethodDefinitionSource(parserContext, delegates, source);
// Add the expression-based after invocation provider
ConfigUtils.getRegisteredAfterInvocationProviders(parserContext).add(
new RootBeanDefinition(MethodExpressionAfterInvocationProvider.class));
// Register the applicable AccessDecisionManager, handling the special JSR 250 voter if being used
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
if (!StringUtils.hasText(accessManagerId)) {
ConfigUtils.registerDefaultMethodAccessManagerIfNecessary(parserContext);
if (jsr250Enabled) {
ConfigUtils.addVoter(new RootBeanDefinition(JSR_250_VOTER_CLASS, null, null), parserContext);
}
accessManagerId = BeanIds.METHOD_ACCESS_MANAGER;
registerAccessManager(element, parserContext, jsr250Enabled, expressionsEnabled);
accessManagerId = ACCESS_MANAGER_ID;
}
registerMethodSecurityInterceptor(parserContext, accessManagerId, source);
@@ -93,38 +117,58 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
/**
* Checks whether el-based, JSR-250 and/or Secured annotations are enabled and adds the appropriate
* MethodDefinitionSource delegates if required.
* 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.
*/
private boolean registerAnnotationBasedMethodDefinitionSources(Element element, ParserContext pc, ManagedList delegates) {
boolean useJsr250 = "enabled".equals(element.getAttribute(ATT_USE_JSR250));
boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED));
boolean useExpressions = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS));
private void registerAccessManager(Element element, ParserContext pc, boolean jsr250Enabled, boolean expressionsEnabled) {
Element permissionEvaluatorElt = DomUtils.getChildElementByTagName(element, Elements.PERMISSON_EVALUATOR);
BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class);
ManagedList voters = new ManagedList(4);
if (useExpressions) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
if (expressionsEnabled) {
BeanDefinitionBuilder expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityExpressionHandler.class);
BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class);
BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class);
if (permissionEvaluatorElt != null) {
String ref = permissionEvaluatorElt.getAttribute("ref");
logger.info("Using bean '" + ref + "' as PermissionEvaluator implementation");
expressionHandler.addPropertyReference("permissionEvaluator", ref);
} else {
logger.warn("Expressions were enabled but no PermissionEvaluator was configured. " +
"All hasPermision() expressions will evaluate to false.");
}
pc.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, expressionHandler.getBeanDefinition());
expressionVoter.addPropertyReference("expressionHandler", EXPRESSION_HANDLER_ID);
afterInvocationProvider.addPropertyReference("expressionHandler", EXPRESSION_HANDLER_ID);
ConfigUtils.getRegisteredAfterInvocationProviders(pc).add(afterInvocationProvider.getBeanDefinition());
voters.add(expressionVoter.getBeanDefinition());
}
if (useSecured) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(SECURED_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
if (jsr250Enabled) {
voters.add(new RootBeanDefinition(JSR_250_VOTER_CLASS, null, null));
}
if (useJsr250) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
}
accessMgrBuilder.addPropertyValue("decisionVoters", voters);
return useJsr250;
pc.getRegistry().registerBeanDefinition(ACCESS_MANAGER_ID, accessMgrBuilder.getBeanDefinition());
}
private void registerDelegatingMethodDefinitionSource(ParserContext parserContext, ManagedList delegates, Object source) {
if (parserContext.getRegistry().containsBeanDefinition(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE)) {
if (parserContext.getRegistry().containsBeanDefinition(DELEGATING_METHOD_DEFINITION_SOURCE_ID)) {
parserContext.getReaderContext().error("Duplicate <global-method-security> detected.", source);
}
RootBeanDefinition delegatingMethodDefinitionSource = new RootBeanDefinition(DelegatingMethodDefinitionSource.class);
delegatingMethodDefinitionSource.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
delegatingMethodDefinitionSource.setSource(source);
delegatingMethodDefinitionSource.getPropertyValues().addPropertyValue("methodDefinitionSources", delegates);
parserContext.getRegistry().registerBeanDefinition(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE, delegatingMethodDefinitionSource);
parserContext.getRegistry().registerBeanDefinition(DELEGATING_METHOD_DEFINITION_SOURCE_ID, delegatingMethodDefinitionSource);
}
private void registerProtectPointcutPostProcessor(ParserContext parserContext, Map pointcutMap,
@@ -173,11 +217,11 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
interceptor.getPropertyValues().addPropertyValue("accessDecisionManager", new RuntimeBeanReference(accessManagerId));
interceptor.getPropertyValues().addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR, interceptor);
parserContext.registerComponent(new BeanComponentDefinition(interceptor, BeanIds.METHOD_SECURITY_INTERCEPTOR));
interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(DELEGATING_METHOD_DEFINITION_SOURCE_ID));
parserContext.getRegistry().registerBeanDefinition(SECURITY_INTERCEPTOR_ID, interceptor);
parserContext.registerComponent(new BeanComponentDefinition(interceptor, SECURITY_INTERCEPTOR_ID));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR,
parserContext.getRegistry().registerBeanDefinition(INTERCEPTOR_POST_PROCESSOR_ID,
new RootBeanDefinition(MethodSecurityInterceptorPostProcessor.class));
}
@@ -185,8 +229,8 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition advisor = new RootBeanDefinition(MethodDefinitionSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.setSource(source);
advisor.getConstructorArgumentValues().addGenericArgumentValue(BeanIds.METHOD_SECURITY_INTERCEPTOR);
advisor.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE));
advisor.getConstructorArgumentValues().addGenericArgumentValue(SECURITY_INTERCEPTOR_ID);
advisor.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(DELEGATING_METHOD_DEFINITION_SOURCE_ID));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_DEFINITION_SOURCE_ADVISOR, advisor);
}
@@ -10,12 +10,12 @@ import org.springframework.security.AfterInvocationManager;
import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor;
/**
* BeanPostProcessor which sets the AfterInvocationManager on the default MethodSecurityInterceptor,
* if one has been configured.
*
* BeanPostProcessor which sets the AfterInvocationManager on the global MethodSecurityInterceptor,
* if one has been configured.
*
* @author Luke Taylor
* @version $Id$
*
*
*/
public class MethodSecurityInterceptorPostProcessor implements BeanPostProcessor, BeanFactoryAware{
private Log logger = LogFactory.getLog(getClass());
@@ -23,26 +23,26 @@ public class MethodSecurityInterceptorPostProcessor implements BeanPostProcessor
private BeanFactory beanFactory;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(!BeanIds.METHOD_SECURITY_INTERCEPTOR.equals(beanName)) {
if(!GlobalMethodSecurityBeanDefinitionParser.SECURITY_INTERCEPTOR_ID.equals(beanName)) {
return bean;
}
MethodSecurityInterceptor interceptor = (MethodSecurityInterceptor) bean;
if (beanFactory.containsBean(BeanIds.AFTER_INVOCATION_MANAGER)) {
logger.debug("Setting AfterInvocationManaer on MethodSecurityInterceptor");
interceptor.setAfterInvocationManager((AfterInvocationManager)
beanFactory.getBean(BeanIds.AFTER_INVOCATION_MANAGER));
logger.debug("Setting AfterInvocationManaer on MethodSecurityInterceptor");
interceptor.setAfterInvocationManager((AfterInvocationManager)
beanFactory.getBean(BeanIds.AFTER_INVOCATION_MANAGER));
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
@@ -6,6 +6,8 @@ import java.util.HashSet;
import java.util.Set;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
@@ -14,7 +16,22 @@ import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationTrustResolver;
import org.springframework.security.AuthenticationTrustResolverImpl;
/**
* The standard implementation of <tt>SecurityExpressionHandler</tt> which uses a {@link SecurityEvaluationContext}
* as the <tt>EvaluationContext</tt> implementation and configures it with a {@link SecurityExpressionRoot} instance
* as the expression root object.
* <p>
* A single instance should usually be shared between the expression voter and after-invocation provider.
*
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class DefaultSecurityExpressionHandler implements SecurityExpressionHandler {
protected final Log logger = LogFactory.getLog(getClass());
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@@ -32,11 +49,20 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
return ctx;
}
public Object doFilter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
SecurityExpressionRoot rootObject = (SecurityExpressionRoot) ctx.getRootContextObject();
Set removeList = new HashSet();
if (logger.isDebugEnabled()) {
logger.debug("Filtering with expression: " + filterExpression.getExpressionString());
}
if (filterTarget instanceof Collection) {
Collection collection = (Collection)filterTarget;
if (logger.isDebugEnabled()) {
logger.debug("Filtering collection with " + collection.size() + " elements");
}
for (Object filterObject : (Collection)filterTarget) {
rootObject.setFilterObject(filterObject);
@@ -45,6 +71,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
}
}
if (logger.isDebugEnabled()) {
logger.debug("Removing elements: " + removeList);
}
for(Object toRemove : removeList) {
((Collection)filterTarget).remove(toRemove);
}
@@ -55,6 +85,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
if (filterTarget.getClass().isArray()) {
Object[] array = (Object[])filterTarget;
if (logger.isDebugEnabled()) {
logger.debug("Filtering collection with " + array.length + " elements");
}
for (int i = 0; i < array.length; i++) {
rootObject.setFilterObject(array[i]);
@@ -63,6 +97,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
}
}
if (logger.isDebugEnabled()) {
logger.debug("Removing elements: " + removeList);
}
Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass().getComponentType(),
array.length - removeList.size());
for (int i = 0, j = 0; i < array.length; i++) {
@@ -2,6 +2,8 @@ package org.springframework.security.expression;
import java.io.Serializable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.Authentication;
/**
@@ -12,12 +14,15 @@ import org.springframework.security.Authentication;
* @version $Id$
* @since 2.5
*/
public class DenyAllPermissionEvaluator implements PermissionEvaluator {
public final class DenyAllPermissionEvaluator implements PermissionEvaluator {
private final Log logger = LogFactory.getLog(getClass());
/**
* @return false always
*/
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
public boolean hasPermission(Authentication authentication, Object target, Object permission) {
logger.warn("Denying user " + authentication.getName() + " permission '" + permission + "' on object " + target);
return false;
}
@@ -26,6 +31,8 @@ public class DenyAllPermissionEvaluator implements PermissionEvaluator {
*/
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
logger.warn("Denying user " + authentication.getName() + " permission '" + permission + "' on object with Id '"
+ targetId);
return false;
}
@@ -30,10 +30,10 @@ public interface SecurityExpressionHandler {
* {@link #createEvaluationContext(Authentication, MethodInvocation)}
* @return the filtered collection or array
*/
Object doFilter(Object filterTarget, Expression filterExpression, EvaluationContext ctx);
Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx);
/**
* Used to inform the expression system of the return object
* Used to inform the expression system of the return object for the given evaluation context.
*
* @param returnObject the return object value
* @param ctx the context within which the object should be set
@@ -33,7 +33,7 @@ public class SecurityExpressionRoot {
public final String write = "write";
public final String create = "create";
public final String delete = "delete";
public final String admin = "admin";
public final String admin = "administration";
SecurityExpressionRoot(Authentication a) {
@@ -51,7 +51,7 @@ public class MethodExpressionAfterInvocationProvider implements AfterInvocationP
}
if (returnedObject != null) {
returnedObject = expressionHandler.doFilter(returnedObject, postFilter, ctx);
returnedObject = expressionHandler.filter(returnedObject, postFilter, ctx);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Return object is null, filtering will be skipped");
@@ -90,6 +90,7 @@ public class MethodExpressionAfterInvocationProvider implements AfterInvocationP
return clazz.isAssignableFrom(MethodInvocation.class);
}
public void setExpressionHandler(SecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
@@ -55,7 +55,7 @@ public class MethodExpressionVoter implements AccessDecisionVoter {
if (preFilter != null) {
Object filterTarget = findFilterTarget(mace.getFilterTarget(), ctx, mi);
expressionHandler.doFilter(filterTarget, preFilter, ctx);
expressionHandler.filter(filterTarget, preFilter, ctx);
}
if (preAuthorize == null) {
@@ -104,4 +104,8 @@ public class MethodExpressionVoter implements AccessDecisionVoter {
return null;
}
public void setExpressionHandler(SecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
@@ -181,10 +181,10 @@ 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, protect-pointcut*}
element global-method-security {global-method-security.attlist, permission-evaluator?, 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 spel-annotations {"disabled" | "enabled" }?
attribute expression-annotations {"disabled" | "enabled" }?
global-method-security.attlist &=
## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "disabled".
attribute secured-annotations {"disabled" | "enabled" }?
@@ -195,6 +195,10 @@ 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}
custom-after-invocation-provider =
## Used to decorate an AfterInvocationProvider to specify that it should be used with method security.
element custom-after-invocation-provider {empty}
@@ -565,6 +565,7 @@
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="security:permission-evaluator"/>
<xs:element minOccurs="0" maxOccurs="unbounded" name="protect-pointcut">
<xs:annotation>
<xs:documentation>Defines a protected pointcut and the access control configuration
@@ -581,7 +582,7 @@
</xs:complexType>
</xs:element>
<xs:attributeGroup name="global-method-security.attlist">
<xs:attribute name="spel-annotations">
<xs:attribute name="expression-annotations">
<xs:annotation>
<xs:documentation>Specifies whether the use of Spring Security's expression-based
annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for
@@ -626,6 +627,15 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="permission-evaluator">
<xs:annotation>
<xs:documentation>Defines the PermissionEvaluator implementation which will be used to
evaluate calls to hasPermission() expressions</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:ref"/>
</xs:complexType>
</xs:element>
<xs:element name="custom-after-invocation-provider">
<xs:annotation>
<xs:documentation>Used to decorate an AfterInvocationProvider to specify that it should be
@@ -30,11 +30,11 @@ public class CustomAfterInvocationProviderBeanDefinitionDecoratorTests {
ConfigTestUtils.AUTH_PROVIDER_XML
);
MethodSecurityInterceptor msi = (MethodSecurityInterceptor) appContext.getBean(BeanIds.METHOD_SECURITY_INTERCEPTOR);
MethodSecurityInterceptor msi = (MethodSecurityInterceptor) appContext.getBean(GlobalMethodSecurityBeanDefinitionParser.SECURITY_INTERCEPTOR_ID);
AfterInvocationProviderManager apm = (AfterInvocationProviderManager) msi.getAfterInvocationManager();
assertNotNull(apm);
assertEquals(2, apm.getProviders().size());
assertTrue(apm.getProviders().get(1) instanceof MockAfterInvocationProvider);
assertEquals(1, apm.getProviders().size());
assertTrue(apm.getProviders().get(0) instanceof MockAfterInvocationProvider);
}
private void setContext(String context) {
@@ -11,11 +11,13 @@ import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationCredentialsNotFoundException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.annotation.BusinessService;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.util.InMemoryXmlApplicationContext;
@@ -69,8 +71,9 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
@Test(expected=AccessDeniedException.class)
public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() {
loadContext();
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_SOMEOTHERROLE")});
TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password", "ROLE_SOMEOTHERROLE");
token.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(token);
target.someAdminMethod();
@@ -186,7 +189,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
@Test(expected=AccessDeniedException.class)
public void accessIsDeniedForHasRoleExpression() {
setContext(
"<global-method-security spel-annotations='enabled'/>" +
"<global-method-security expression-annotations='enabled'/>" +
"<b:bean id='target' class='org.springframework.security.annotation.ExpressionProtectedBusinessServiceImpl'/>" +
AUTH_PROVIDER_XML);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword"));
@@ -197,7 +200,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
@Test
public void preAndPostFilterAnnotationsWorkWithLists() {
setContext(
"<global-method-security spel-annotations='enabled'/>" +
"<global-method-security expression-annotations='enabled'/>" +
"<b:bean id='target' class='org.springframework.security.annotation.ExpressionProtectedBusinessServiceImpl'/>" +
AUTH_PROVIDER_XML);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword"));
@@ -216,7 +219,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
@Test
public void prePostFilterAnnotationWorksWithArrays() {
setContext(
"<global-method-security spel-annotations='enabled'/>" +
"<global-method-security expression-annotations='enabled'/>" +
"<b:bean id='target' class='org.springframework.security.annotation.ExpressionProtectedBusinessServiceImpl'/>" +
AUTH_PROVIDER_XML);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword"));