SEC-99/428/429/563: Various refactoring of method security metadata support.

This commit is contained in:
Ben Alex
2008-03-24 09:40:13 +00:00
parent beba7221cf
commit 9a4977ebd1
33 changed files with 940 additions and 1020 deletions

View File

@@ -15,15 +15,14 @@
package org.springframework.security;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.util.Assert;
/**
@@ -99,6 +98,33 @@ public class ConfigAttributeDefinition implements Serializable {
this.configAttributes = Collections.unmodifiableList(new ArrayList(configAttributes));
}
/**
* Creates a <tt>ConfigAttributeDefinition</tt> by including only those attributes which implement <tt>ConfigAttribute</tt>.
*
* @param unfilteredInput a collection of various elements, zero or more which implement <tt>ConfigAttribute</tt> (can also be <tt>null</tt>)
* @return a ConfigAttributeDefinition if at least one <tt>ConfigAttribute</tt> was present, or <tt>null</tt> if none implemented it
*/
public static ConfigAttributeDefinition createFiltered(Collection unfilteredInput) {
if (unfilteredInput == null) {
return null;
}
List configAttributes = new ArrayList();
Iterator i = unfilteredInput.iterator();
while (i.hasNext()) {
Object element = i.next();
if (element instanceof ConfigAttribute) {
configAttributes.add(element);
}
}
if (configAttributes.size() == 0) {
return null;
}
return new ConfigAttributeDefinition(configAttributes);
}
//~ Methods ========================================================================================================

View File

@@ -23,8 +23,8 @@ import org.w3c.dom.Element;
* @version $Id$
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public static final String SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.SecurityAnnotationAttributes";
public static final String JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.Jsr250SecurityAnnotationAttributes";
public static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource";
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 static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
private static final String ATT_USE_JSR250 = "jsr250";
@@ -32,10 +32,12 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
boolean useJsr250 = "true".equals(element.getAttribute(ATT_USE_JSR250));
String className = useJsr250 ?
JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS : SECURITY_ANNOTATION_ATTRIBUTES_CLASS;
JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS : SECURED_METHOD_DEFINITION_SOURCE_CLASS;
String beanId = useJsr250 ? BeanIds.JSR_250_METHOD_DEFINITION_SOURCE : BeanIds.SECURED_METHOD_DEFINITION_SOURCE;
// Reflectively obtain the Annotation-based ObjectDefinitionSource.
// Reflection is used to avoid a compile-time dependency on SECURITY_ANNOTATION_ATTRIBUTES_CLASS, as this parser is in the Java 4 project whereas the dependency is in the Tiger project.
// Reflection is used to avoid a compile-time dependency on SECURED_METHOD_DEFINITION_SOURCE_CLASS, as this parser is in the Java 4 project whereas the dependency is in the Tiger project.
Assert.isTrue(ClassUtils.isPresent(className), "Could not locate class '" + className + "' - please ensure the spring-security-tiger-xxx.jar is in your classpath and you are running Java 5 or above.");
Class clazz = null;
@@ -47,15 +49,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition securityAnnotations = new RootBeanDefinition(clazz);
securityAnnotations.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(BeanIds.SECURITY_ANNOTATION_ATTRIBUTES, securityAnnotations);
RootBeanDefinition methodDefinitionAttributes = new RootBeanDefinition(MethodDefinitionAttributes.class);
methodDefinitionAttributes.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodDefinitionAttributes.getPropertyValues().addPropertyValue("attributes", new RuntimeBeanReference(BeanIds.SECURITY_ANNOTATION_ATTRIBUTES));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_DEFINITION_ATTRIBUTES, methodDefinitionAttributes);
RootBeanDefinition interceptor = new RootBeanDefinition(MethodSecurityInterceptor.class);
interceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(beanId, securityAnnotations);
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
@@ -69,15 +63,22 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
accessManagerId = BeanIds.ACCESS_MANAGER;
}
// MethodSecurityInterceptor
RootBeanDefinition interceptor = new RootBeanDefinition(MethodSecurityInterceptor.class);
interceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
interceptor.getPropertyValues().addPropertyValue("accessDecisionManager",
new RuntimeBeanReference(accessManagerId));
interceptor.getPropertyValues().addPropertyValue("authenticationManager",
new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(BeanIds.METHOD_DEFINITION_ATTRIBUTES));
interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(beanId));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR, interceptor);
// MethodDefinitionSourceAdvisor
RootBeanDefinition advisor = new RootBeanDefinition(MethodDefinitionSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor);

View File

@@ -48,8 +48,8 @@ public abstract class BeanIds {
public static final String SECURITY_CONTEXT_HOLDER_AWARE_REQUEST_FILTER = "_securityContextHolderAwareRequestFilter";
public static final String METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor";
public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor";
public static final String SECURITY_ANNOTATION_ATTRIBUTES = "_securityAnnotationAttributes";
public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes";
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";
public static final String CONTEXT_SOURCE = "_securityContextSource";
public static final String PORT_MAPPER = "_portMapper";

View File

@@ -1,28 +1,24 @@
package org.springframework.security.config;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.config.AbstractInterceptorDrivenBeanDefinitionDecorator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.intercept.method.MethodDefinitionMap;
import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor;
import org.springframework.util.xml.DomUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.Iterator;
import java.util.List;
/**
* @author Luke Taylor
* @author Ben Alex
@@ -35,17 +31,16 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
ConfigUtils.registerProviderManagerIfNecessary(parserContext);
ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext);
return delegate.decorate(node, definition, parserContext);
}
}
/**
* This is the real class which does the work. We need acccess to the ParserContext in order to do bean
* This is the real class which does the work. We need access to the ParserContext in order to do bean
* registration.
*/
class InternalInterceptMethodsBeanDefinitionDecorator extends AbstractInterceptorDrivenBeanDefinitionDecorator {
static final String ATT_CLASS = "class";
static final String ATT_METHOD = "method";
static final String ATT_ACCESS = "access";
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
@@ -68,24 +63,35 @@ class InternalInterceptMethodsBeanDefinitionDecorator extends AbstractIntercepto
interceptor.addPropertyValue("accessDecisionManager", new RuntimeBeanReference(accessManagerId));
interceptor.addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
// Lookup parent bean information
Element parent = (Element) node.getParentNode();
String parentBeanClass = parent.getAttribute("class");
String parentBeanId = parent.getAttribute("id");
parent = null;
// Parse the included methods
List methods = DomUtils.getChildElementsByTagName(interceptMethodsElt, Elements.PROTECT);
MethodDefinitionMap methodMap = new MethodDefinitionMap();
ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();
StringBuffer sb = new StringBuffer();
for (Iterator i = methods.iterator(); i.hasNext();) {
Element protectmethodElt = (Element) i.next();
String accessConfig = protectmethodElt.getAttribute(ATT_ACCESS);
attributeEditor.setAsText(accessConfig);
// TODO: We want to use just the method names, but MethodDefinitionMap won't work that way.
// methodMap.addSecureMethod(targetClass, protectmethodElt.getAttribute("method"),
// (ConfigAttributeDefinition) attributeEditor.getValue());
methodMap.addSecureMethod(protectmethodElt.getAttribute(ATT_METHOD),
(ConfigAttributeDefinition) attributeEditor.getValue());
// Support inference of class names
String methodName = protectmethodElt.getAttribute(ATT_METHOD);
if (methodName.lastIndexOf(".") == -1) {
if (parentBeanClass != null && !"".equals(parentBeanClass)) {
methodName = parentBeanClass + "." + methodName;
}
}
// Rely on the default property editor for MethodSecurityInterceptor.setObjectDefinitionSource to setup the MethodDefinitionSource
sb.append(methodName + "=" + accessConfig).append("\r\n");
}
interceptor.addPropertyValue("objectDefinitionSource", methodMap);
interceptor.addPropertyValue("objectDefinitionSource", sb.toString());
return interceptor.getBeanDefinition();
}

View File

@@ -0,0 +1,200 @@
package org.springframework.security.intercept.method;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Abstract implementation of {@link MethodDefinitionSource} that supports both Spring AOP and AspectJ and
* caches configuration attribute resolution from: 1. specific target method; 2. target class; 3. declaring method;
* 4. declaring class/interface.
*
* <p>
* This class mimics the behaviour of Spring's AbstractFallbackTransactionAttributeSource class.
* </p>
*
* <p>
* Note that this class cannot extract security metadata where that metadata is expressed by way of
* a target method/class (ie #1 and #2 above) AND the target method/class is encapsulated in another
* proxy object. Spring Security does not walk a proxy chain to locate the concrete/final target object.
* Consider making Spring Security your final advisor (so it advises the final target, as opposed to
* another proxy), move the metadata to declared methods or interfaces the proxy implements, or provide
* your own replacement <tt>MethodDefinitionSource</tt>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public abstract class AbstractFallbackMethodDefinitionSource implements MethodDefinitionSource {
private static final Log logger = LogFactory.getLog(AbstractFallbackMethodDefinitionSource.class);
private final static Object NULL_CONFIG_ATTRIBUTE = new Object();
private final Map attributeCache = new HashMap();
public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
Assert.notNull(object, "Object cannot be null");
if (object instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) object;
return getAttributes(mi.getMethod(), mi.getThis().getClass());
}
if (object instanceof JoinPoint) {
JoinPoint jp = (JoinPoint) object;
Class targetClass = jp.getTarget().getClass();
String targetMethodName = jp.getStaticPart().getSignature().getName();
Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
Class declaringType = ((CodeSignature) jp.getStaticPart().getSignature()).getDeclaringType();
Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types);
Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'");
return getAttributes(method, targetClass);
}
throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint");
}
public final boolean supports(Class clazz) {
return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz));
}
public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) {
// First, see if we have a cached value.
Object cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Object cached = this.attributeCache.get(cacheKey);
if (cached != null) {
// Value will either be canonical value indicating there is no config attribute,
// or an actual config attribute.
if (cached == NULL_CONFIG_ATTRIBUTE) {
return null;
}
else {
return (ConfigAttributeDefinition) cached;
}
}
else {
// We need to work it out.
ConfigAttributeDefinition cfgAtt = computeAttributes(method, targetClass);
// Put it in the cache.
if (cfgAtt == null) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Adding security method [" + cacheKey + "] with attribute [" + cfgAtt + "]");
}
this.attributeCache.put(cacheKey, cfgAtt);
}
return cfgAtt;
}
}
}
/**
*
* @param method the method for the current invocation (never <code>null</code>)
* @param targetClass the target class for this invocation (may be <code>null</code>)
* @return
*/
private ConfigAttributeDefinition computeAttributes(Method method, Class targetClass) {
// 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);
// First try is the method in the target class.
ConfigAttributeDefinition attr = findAttributes(specificMethod, targetClass);
if (attr != null) {
return attr;
}
// Second try is the config attribute on the target class.
attr = findAttributes(specificMethod.getDeclaringClass());
if (attr != null) {
return attr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
attr = findAttributes(method, method.getDeclaringClass());
if (attr != null) {
return attr;
}
// Last fallback is the class of the original method.
return findAttributes(method.getDeclaringClass());
}
return null;
}
/**
* Obtains the security metadata applicable to the specified method invocation.
*
* <p>
* Note that the {@link Method#getDeclaringClass()} may not equal the <code>targetClass</code>.
* Both parameters are provided to assist subclasses which may wish to provide advanced
* capabilities related to method metadata being "registered" against a method even if the
* target class does not declare the method (ie the subclass may only inherit the method).
*
* @param method the method for the current invocation (never <code>null</code>)
* @param targetClass the target class for the invocation (may be <code>null</code>)
* @return the security metadata (or null if no metadata applies)
*/
protected abstract ConfigAttributeDefinition findAttributes(Method method, Class targetClass);
/**
* Obtains the security metadata registered against the specified class.
*
* <p>
* Subclasses should only return metadata expressed at a class level. Subclasses should NOT
* aggregate metadata for each method registered against a class, as the abstract superclass
* will separate invoke {@link #findAttributes(Method, Class)} for individual methods as
* appropriate.
*
* @param clazz the target class for the invocation (never <code>null</code>)
* @return the security metadata (or null if no metadata applies)
*/
protected abstract ConfigAttributeDefinition findAttributes(Class clazz);
private static class DefaultCacheKey {
private final Method method;
private final Class targetClass;
public DefaultCacheKey(Method method, Class targetClass) {
this.method = method;
this.targetClass = targetClass;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof DefaultCacheKey)) {
return false;
}
DefaultCacheKey otherKey = (DefaultCacheKey) other;
return (this.method.equals(otherKey.method) &&
ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
}
public int hashCode() {
return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
}
public String toString() {
return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]";
}
}
}

View File

@@ -15,63 +15,59 @@
package org.springframework.security.intercept.method;
import org.springframework.security.ConfigAttributeDefinition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Stores a {@link ConfigAttributeDefinition} for each method signature defined in a bean context.<p>For
* consistency with {@link MethodDefinitionAttributes} as well as support for
* <code>MethodDefinitionSourceAdvisor</code>, this implementation will return a
* <code>ConfigAttributeDefinition</code> containing all configuration attributes defined against:
* <ul>
* <li>The method-specific attributes defined for the intercepted method of the intercepted class.</li>
* <li>The method-specific attributes defined by any explicitly implemented interface if that interface
* contains a method signature matching that of the intercepted method.</li>
* </ul>
* </p>
* <p>In general you should therefore define the <b>interface method</b>s of your secure objects, not the
* implementations. For example, define <code>com.company.Foo.findAll=ROLE_TEST</code> but not
* <code>com.company.FooImpl.findAll=ROLE_TEST</code>.</p>
*
* Stores a {@link ConfigAttributeDefinition} for a method or class signature.
*
* <p>
* This class is the preferred implementation of {@link MethodDefinitionSource} for XML-based
* definition of method security metadata. To assist in XML-based definition, wildcard support
* is provided.
* </p>
*
* @author Ben Alex
* @version $Id$
* @version $Id: MethodDefinitionMap.java 2558 2008-01-30 15:43:40Z luke_t $
*/
public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource implements BeanClassLoaderAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(MethodDefinitionMap.class);
private static final Log logger = LogFactory.getLog(MapBasedMethodDefinitionSource.class);
//~ Instance fields ================================================================================================
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
/** Map from Method to ApplicationDefinition */
/** Map from RegisteredMethod to ConfigAttributeDefinition */
protected Map methodMap = new HashMap();
/** Map from Method to name pattern used for registration */
/** Map from RegisteredMethod to name pattern used for registration */
private Map nameMap = new HashMap();
//~ Methods ========================================================================================================
public MethodDefinitionMap() {
public MapBasedMethodDefinitionSource() {
}
/**
* Creates the MethodDefinitionMap from a
* Creates the MapBasedMethodDefinitionSource from a
* @param methodMap map of method names to <tt>ConfigAttributeDefinition</tt>s.
*/
public MethodDefinitionMap(Map methodMap) {
public MapBasedMethodDefinitionSource(Map methodMap) {
Iterator iterator = methodMap.entrySet().iterator();
while (iterator.hasNext()) {
@@ -80,15 +76,44 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
}
}
/**
* Implementation does not support class-level attributes.
*/
protected ConfigAttributeDefinition findAttributes(Class clazz) {
return null;
}
/**
* Will walk the method inheritance tree to find the most specific declaration applicable.
*/
protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) {
return findAttributesSpecifiedAgainst(method, targetClass);
}
private ConfigAttributeDefinition findAttributesSpecifiedAgainst(Method method, Class clazz) {
RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz);
if (methodMap.containsKey(registeredMethod)) {
return (ConfigAttributeDefinition) methodMap.get(registeredMethod);
}
// Search superclass
if (clazz.getSuperclass() != null) {
return findAttributesSpecifiedAgainst(method, clazz.getSuperclass());
}
return null;
}
/**
* Add configuration attributes for a secure method. Method names can end or start with <code>&#42</code>
* for matching multiple methods.
* Add configuration attributes for a secure method.
*
* @param method the method to be secured
* @param attr required authorities associated with the method
*/
public void addSecureMethod(Method method, ConfigAttributeDefinition attr) {
logger.info("Adding secure method [" + method + "] with attributes [" + attr + "]");
private void addSecureMethod(RegisteredMethod method, ConfigAttributeDefinition attr) {
Assert.notNull(method, "RegisteredMethod required");
Assert.notNull(attr, "Configuration attribute required");
if (logger.isInfoEnabled()) {
logger.info("Adding secure method [" + method + "] with attributes [" + attr + "]");
}
this.methodMap.put(method, attr);
}
@@ -96,47 +121,41 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
* Add configuration attributes for a secure method. Method names can end or start with <code>&#42</code>
* for matching multiple methods.
*
* @param name class and method name, separated by a dot
* @param name type and method name, separated by a dot
* @param attr required authorities associated with the method
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
int lastDotIndex = name.lastIndexOf(".");
int lastDotIndex = name.lastIndexOf(".");
if (lastDotIndex == -1) {
throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName");
}
String className = name.substring(0, lastDotIndex);
String methodName = name.substring(lastDotIndex + 1);
try {
Class clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
addSecureMethod(clazz, methodName, attr);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Class '" + className + "' not found");
}
Assert.hasText(methodName, "Method not found for '" + name + "'");
String typeName = name.substring(0, lastDotIndex);
Class type = ClassUtils.resolveClassName(typeName, this.beanClassLoader);
addSecureMethod(type, methodName, attr);
}
/**
* Add configuration attributes for a secure method. Method names can end or start with <code>&#42</code>
* Add configuration attributes for a secure method. Mapped method names can end or start with <code>&#42</code>
* for matching multiple methods.
*
* @param clazz target interface or class
* @param mappedName mapped method name
*
* @param javaType target interface or class the security configuration attribute applies to
* @param mappedName mapped method name, which the javaType has declared or inherited
* @param attr required authorities associated with the method
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public void addSecureMethod(Class clazz, String mappedName, ConfigAttributeDefinition attr) {
String name = clazz.getName() + '.' + mappedName;
public void addSecureMethod(Class javaType, String mappedName, ConfigAttributeDefinition attr) {
String name = javaType.getName() + '.' + mappedName;
if (logger.isDebugEnabled()) {
logger.debug("Adding secure method [" + name + "] with attributes [" + attr + "]");
logger.debug("Request to add secure method [" + name + "] with attributes [" + attr + "]");
}
Method[] methods = clazz.getDeclaredMethods();
Method[] methods = javaType.getMethods();
List matchingMethods = new ArrayList();
for (int i = 0; i < methods.length; i++) {
@@ -146,13 +165,14 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
}
if (matchingMethods.isEmpty()) {
throw new IllegalArgumentException("Couldn't find method '" + mappedName + "' on " + clazz);
throw new IllegalArgumentException("Couldn't find method '" + mappedName + "' on '" + javaType + "'");
}
// register all matching methods
for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
Method method = (Method) it.next();
String regMethodName = (String) this.nameMap.get(method);
RegisteredMethod registeredMethod = new RegisteredMethod(method, javaType);
String regMethodName = (String) this.nameMap.get(registeredMethod);
if ((regMethodName == null) || (!regMethodName.equals(name) && (regMethodName.length() <= name.length()))) {
// no already registered method name, or more specific
@@ -162,8 +182,8 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
+ "] is more specific than [" + regMethodName + "]");
}
this.nameMap.put(method, name);
addSecureMethod(method, attr);
this.nameMap.put(registeredMethod, name);
addSecureMethod(registeredMethod, attr);
} else {
logger.debug("Keeping attributes for secure method [" + method + "]: current name [" + name
+ "] is not more specific than [" + regMethodName + "]");
@@ -172,9 +192,7 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
}
/**
* Obtains the configuration attributes explicitly defined against this bean. This method will not return
* implicit configuration attributes that may be returned by {@link #lookupAttributes(Method)} as it does not have
* access to a method invocation at this time.
* Obtains the configuration attributes explicitly defined against this bean.
*
* @return the attributes explicitly defined against this bean
*/
@@ -182,17 +200,6 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
return Collections.unmodifiableCollection(methodMap.values());
}
/**
* Obtains the number of configuration attributes explicitly defined against this bean. This method will
* not return implicit configuration attributes that may be returned by {@link #lookupAttributes(Method)} as it
* does not have access to a method invocation at this time.
*
* @return the number of configuration attributes explicitly defined against this bean
*/
public int getMethodMapSize() {
return this.methodMap.size();
}
/**
* Return if the given method name matches the mapped name. The default implementation checks for "xxx" and
* "xxx" matches.
@@ -245,4 +252,55 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
attributes.addAll(toMerge.getConfigAttributes());
}
public void setBeanClassLoader(ClassLoader beanClassLoader) {
Assert.notNull(beanClassLoader, "Bean class loader required");
this.beanClassLoader = beanClassLoader;
}
/**
* @return map size (for unit tests and diagnostics)
*/
public int getMethodMapSize() {
return methodMap.size();
}
/**
* Stores both the Java Method as well as the Class we obtained the Method from. This is necessary because Method only
* provides us access to the declaring class. It doesn't provide a way for us to introspect which Class the Method
* was registered against. If a given Class inherits and redeclares a method (ie calls super();) the registered Class
* and delcaring Class are the same. If a given class merely inherits but does not redeclare a method, the registered
* Class will be the Class we're invoking against and the Method will provide details of the declared class.
*/
private class RegisteredMethod {
private Method method;
private Class registeredJavaType;
public RegisteredMethod(Method method, Class registeredJavaType) {
Assert.notNull(method, "Method required");
Assert.notNull(registeredJavaType, "Registered Java Type required");
this.method = method;
this.registeredJavaType = registeredJavaType;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof RegisteredMethod) {
RegisteredMethod rhs = (RegisteredMethod) obj;
return method.equals(rhs.method) && registeredJavaType.equals(rhs.registeredJavaType);
}
return false;
}
public int hashCode() {
return method.hashCode() * registeredJavaType.hashCode();
}
public String toString() {
return "RegisteredMethod[" + registeredJavaType.getName() + "; " + method + "]";
}
}
}

View File

@@ -15,122 +15,57 @@
package org.springframework.security.intercept.method;
import java.lang.reflect.Method;
import java.util.Collection;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.metadata.Attributes;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.metadata.Attributes;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import org.springframework.util.Assert;
/**
* Provides {@link ConfigAttributeDefinition}s for a method signature (via the <tt>lookupAttributes</tt> method)
* by delegating to a configured {@link Attributes} object. The latter may use Java 5 annotations, Commons attributes
* by delegating to a configured {@link Attributes} object. The latter may use Commons attributes
* or some other approach to determine the <tt>ConfigAttribute</tt>s which apply.
* <p>
* This class will only detect those attributes which are defined for:
* <ul>
* <li>The class-wide attributes defined for the intercepted class.</li>
* <li>The class-wide attributes defined for interfaces explicitly implemented by the intercepted class.</li>
* <li>The method-specific attributes defined for the intercepted method of the intercepted class.</li>
* <li>The method-specific attributes defined by any explicitly implemented interface if that interface
* contains a method signature matching that of the intercepted method.</li>
* </ul>
*
* <p>
* Note that attributes defined against parent classes (either for their methods or interfaces) are not
* detected. The attributes must be defined against an explicit method or interface on the intercepted class.
* <p>
*
* Attributes detected that do not implement {@link ConfigAttribute} will be ignored.
*
* @author Cameron Braid
* @author Ben Alex
* @version $Id$
*/
public class MethodDefinitionAttributes extends AbstractMethodDefinitionSource {
public class MethodDefinitionAttributes extends AbstractFallbackMethodDefinitionSource implements InitializingBean {
//~ Instance fields ================================================================================================
private Attributes attributes;
//~ Methods ========================================================================================================
private void add(List definition, Collection attribs) {
for (Iterator iter = attribs.iterator(); iter.hasNext();) {
Object o = iter.next();
if (o instanceof ConfigAttribute) {
definition.add(o);
}
}
}
private void addClassAttributes(List definition, Class[] clazz) {
for (int i = 0; i < clazz.length; i++) {
Collection classAttributes = attributes.getAttributes(clazz[i]);
if (classAttributes != null) {
add(definition, classAttributes);
}
}
}
private void addInterfaceMethodAttributes(List definition, Method method) {
Class[] interfaces = method.getDeclaringClass().getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class clazz = interfaces[i];
try {
Method m = clazz.getDeclaredMethod(method.getName(), (Class[]) method.getParameterTypes());
addMethodAttributes(definition, m);
} catch (Exception e) {
// this won't happen since we are getting a method from an interface that
// the declaring class implements
}
}
}
private void addMethodAttributes(List definition, Method method) {
// add the method level attributes
Collection methodAttributes = attributes.getAttributes(method);
if (methodAttributes != null) {
add(definition, methodAttributes);
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(attributes, "attributes required");
}
public Collection getConfigAttributeDefinitions() {
return null;
}
protected ConfigAttributeDefinition findAttributes(Class clazz) {
return ConfigAttributeDefinition.createFiltered(attributes.getAttributes(clazz));
}
protected ConfigAttributeDefinition lookupAttributes(Method method) {
Class interceptedClass = method.getDeclaringClass();
List attributes = new ArrayList();
// add the class level attributes for the implementing class
addClassAttributes(attributes, new Class[] {interceptedClass});
// add the class level attributes for the implemented interfaces
addClassAttributes(attributes, interceptedClass.getInterfaces());
// add the method level attributes for the implemented method
addMethodAttributes(attributes, method);
// add the method level attributes for the implemented intreface methods
addInterfaceMethodAttributes(attributes, method);
if (attributes.size() == 0) {
return null;
}
return new ConfigAttributeDefinition(attributes);
}
protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) {
return ConfigAttributeDefinition.createFiltered(attributes.getAttributes(method));
}
public void setAttributes(Attributes attributes) {
Assert.notNull(attributes, "Attributes required");
this.attributes = attributes;
}
}

View File

@@ -15,14 +15,19 @@
package org.springframework.security.intercept.method;
import java.lang.reflect.Method;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.intercept.ObjectDefinitionSource;
/**
* Marker interface for <code>ObjectDefinitionSource</code> implementations
* Interface for <code>ObjectDefinitionSource</code> implementations
* that are designed to perform lookups keyed on <code>Method</code>s.
*
* @author Ben Alex
* @version $Id$
*/
public interface MethodDefinitionSource extends ObjectDefinitionSource {}
public interface MethodDefinitionSource extends ObjectDefinitionSource {
public ConfigAttributeDefinition getAttributes(Method method, Class targetClass);
}

View File

@@ -33,7 +33,7 @@ import java.util.LinkedHashMap;
/**
* Property editor to assist with the setup of a {@link MethodDefinitionSource}.
* <p>The class creates and populates a {@link MethodDefinitionMap}.</p>
* <p>The class creates and populates a {@link MapBasedMethodDefinitionSource}.</p>
*
* @author Ben Alex
* @version $Id$
@@ -47,7 +47,7 @@ public class MethodDefinitionSourceEditor extends PropertyEditorSupport {
public void setAsText(String s) throws IllegalArgumentException {
if ((s == null) || "".equals(s)) {
setValue(new MethodDefinitionMap());
setValue(new MapBasedMethodDefinitionSource());
return;
}
@@ -69,6 +69,6 @@ public class MethodDefinitionSourceEditor extends PropertyEditorSupport {
mappings.put(name, new ConfigAttributeDefinition(tokens));
}
setValue(new MethodDefinitionMap(mappings));
setValue(new MapBasedMethodDefinitionSource(mappings));
}
}

View File

@@ -21,11 +21,10 @@ import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.security.intercept.method.MethodDefinitionSource;
import org.springframework.util.Assert;
/**
* Advisor driven by a {@link MethodDefinitionSource}, used to exclude a {@link MethodSecurityInterceptor} from
@@ -55,10 +54,7 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor {
public MethodDefinitionSourceAdvisor(MethodSecurityInterceptor advice) {
this.interceptor = advice;
if (advice.getObjectDefinitionSource() == null) {
throw new AopConfigException("Cannot construct a MethodDefinitionSourceAdvisor using a "
+ "MethodSecurityInterceptor that has no ObjectDefinitionSource configured");
}
Assert.notNull(advice.getObjectDefinitionSource(), "Cannot construct a MethodDefinitionSourceAdvisor using a MethodSecurityInterceptor that has no ObjectDefinitionSource configured");
this.attributeSource = advice.getObjectDefinitionSource();
this.pointcut = new MethodDefinitionSourcePointcut();
@@ -78,9 +74,7 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor {
class MethodDefinitionSourcePointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
MethodInvocation methodInvocation = new InternalMethodInvocation(m);
return attributeSource.getAttributes(methodInvocation) != null;
return attributeSource.getAttributes(m, targetClass) != null;
}
}
@@ -92,9 +86,11 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor {
*/
class InternalMethodInvocation implements MethodInvocation {
private Method method;
private Class targetClass;
public InternalMethodInvocation(Method method) {
public InternalMethodInvocation(Method method, Class targetClass) {
this.method = method;
this.targetClass = targetClass;
}
protected InternalMethodInvocation() {
@@ -114,7 +110,7 @@ public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor {
}
public Object getThis() {
throw new UnsupportedOperationException();
return this.targetClass;
}
public Object proceed() throws Throwable {

View File

@@ -15,15 +15,15 @@
package org.springframework.security.util;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.Assert;
/**
* Static utility methods for creating <code>MethodInvocation</code>s usable within Spring Security.
@@ -76,8 +76,25 @@ public final class MethodInvocationUtils {
classArgs = (Class[]) list.toArray(new Class[] {});
}
// Determine the type that declares the requested method, taking into account proxies
Class target = AopUtils.getTargetClass(object);
if (object instanceof Advised) {
Advised a = (Advised) object;
if (!a.isProxyTargetClass()) {
Class[] possibleInterfaces = a.getProxiedInterfaces();
for (int i = 0; i < possibleInterfaces.length; i++) {
try {
possibleInterfaces[i].getMethod(methodName, classArgs);
// to get here means no exception happened
target = possibleInterfaces[i];
break;
} catch (Exception tryTheNextOne) {}
}
}
}
return createFromClass(object.getClass(), methodName, classArgs, args);
return createFromClass(object, target, methodName, classArgs, args);
}
/**
@@ -89,21 +106,22 @@ public final class MethodInvocationUtils {
* @return a <code>MethodInvocation</code>, or <code>null</code> if there was a problem
*/
public static MethodInvocation createFromClass(Class clazz, String methodName) {
return createFromClass(clazz, methodName, null, null);
return createFromClass(null, clazz, methodName, null, null);
}
/**
* Generates a <code>MethodInvocation</code> for specified <code>methodName</code> on the passed class,
* using the <code>args</code> to locate the method.
*
* @param targetObject the object being invoked
* @param clazz the class of object that will be used to find the relevant <code>Method</code>
* @param methodName the name of the method to find
* @param classArgs arguments that are required to locate the relevant method signature
* @param args the actual arguments that should be passed to SimpleMethodInvocation
* @return a <code>MethodInvocation</code>, or <code>null</code> if there was a problem
*/
public static MethodInvocation createFromClass(Class clazz, String methodName, Class[] classArgs, Object[] args) {
Assert.notNull(clazz, "Class required");
public static MethodInvocation createFromClass(Object targetObject, Class clazz, String methodName, Class[] classArgs, Object[] args) {
Assert.notNull(clazz, "Class required");
Assert.hasText(methodName, "MethodName required");
Method method;
@@ -114,6 +132,6 @@ public final class MethodInvocationUtils {
return null;
}
return new SimpleMethodInvocation(method, args);
return new SimpleMethodInvocation(targetObject, method, args);
}
}

View File

@@ -32,11 +32,13 @@ public class SimpleMethodInvocation implements MethodInvocation {
private Method method;
private Object[] arguments;
private Object targetObject;
//~ Constructors ===================================================================================================
public SimpleMethodInvocation(Method method, Object[] arguments) {
this.method = method;
public SimpleMethodInvocation(Object targetObject, Method method, Object[] arguments) {
this.targetObject = targetObject;
this.method = method;
this.arguments = arguments;
}
@@ -57,7 +59,7 @@ public class SimpleMethodInvocation implements MethodInvocation {
}
public Object getThis() {
throw new UnsupportedOperationException("mock method not implemented");
return targetObject;
}
public Object proceed() throws Throwable {