mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-25 13:32:30 +00:00
OPEN - issue SEC-905: <protect-pointcut /> pointcuts do not respect method arguments
http://jira.springframework.org/browse/SEC-905. Added extra registration method to MapBasedMethodDefinitionSource which takes a Method instance rather than the method name.
This commit is contained in:
parent
d9ab0758ee
commit
55d357f42d
@ -39,8 +39,10 @@ public interface BusinessService {
|
|||||||
public void someUserMethod1();
|
public void someUserMethod1();
|
||||||
|
|
||||||
@Secured({"ROLE_USER"})
|
@Secured({"ROLE_USER"})
|
||||||
@RolesAllowed({"ROLE_USER"})
|
@RolesAllowed({"ROLE_USER"})
|
||||||
public void someUserMethod2();
|
public void someUserMethod2();
|
||||||
|
|
||||||
|
public int someOther(String s);
|
||||||
|
|
||||||
public int someOther(int input);
|
public int someOther(int input);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,11 @@ public class BusinessServiceImpl<E extends Entity> implements BusinessService {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int someOther(int input) {
|
public int someOther(String s) {
|
||||||
return input;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int someOther(int input) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,11 @@ public class Jsr250BusinessServiceImpl implements BusinessService {
|
|||||||
public void someAdminMethod() {
|
public void someAdminMethod() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int someOther(String input) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public int someOther(int input) {
|
public int someOther(int input) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
|
|||||||
setContext(
|
setContext(
|
||||||
"<b:bean id='target' class='org.springframework.security.annotation.BusinessServiceImpl'/>" +
|
"<b:bean id='target' class='org.springframework.security.annotation.BusinessServiceImpl'/>" +
|
||||||
"<global-method-security>" +
|
"<global-method-security>" +
|
||||||
" <protect-pointcut expression='execution(* *.someUser*(..))' access='ROLE_USER'/>" +
|
" <protect-pointcut expression='execution(* *.someUser*(..))' access='ROLE_USER'/>" +
|
||||||
" <protect-pointcut expression='execution(* *.someAdmin*(..))' access='ROLE_ADMIN'/>" +
|
" <protect-pointcut expression='execution(* *.someAdmin*(..))' access='ROLE_ADMIN'/>" +
|
||||||
"</global-method-security>" + ConfigTestUtils.AUTH_PROVIDER_XML
|
"</global-method-security>" + ConfigTestUtils.AUTH_PROVIDER_XML
|
||||||
);
|
);
|
||||||
target = (BusinessService) appContext.getBean("target");
|
target = (BusinessService) appContext.getBean("target");
|
||||||
@ -106,6 +106,21 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
|
|||||||
service.loadUserByUsername("notused");
|
service.loadUserByUsername("notused");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsMethodArgumentsInPointcut() {
|
||||||
|
setContext(
|
||||||
|
"<b:bean id='target' class='org.springframework.security.annotation.BusinessServiceImpl'/>" +
|
||||||
|
"<global-method-security>" +
|
||||||
|
" <protect-pointcut expression='execution(* *.someOther(String))' access='ROLE_ADMIN'/>" +
|
||||||
|
" <protect-pointcut expression='execution(* *.BusinessService*(..))' access='ROLE_USER'/>" +
|
||||||
|
"</global-method-security>" + ConfigTestUtils.AUTH_PROVIDER_XML
|
||||||
|
);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("user", "password"));
|
||||||
|
target = (BusinessService) appContext.getBean("target");
|
||||||
|
// someOther(int) should not be matched by someOther(String)
|
||||||
|
target.someOther(0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected=BeanDefinitionParsingException.class)
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
public void duplicateElementCausesError() {
|
public void duplicateElementCausesError() {
|
||||||
setContext(
|
setContext(
|
||||||
@ -116,21 +131,21 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
|
|||||||
|
|
||||||
@Test(expected=AccessDeniedException.class)
|
@Test(expected=AccessDeniedException.class)
|
||||||
public void worksWithoutTargetOrClass() {
|
public void worksWithoutTargetOrClass() {
|
||||||
setContext(
|
setContext(
|
||||||
"<global-method-security secured-annotations='enabled'/>" +
|
"<global-method-security secured-annotations='enabled'/>" +
|
||||||
"<b:bean id='businessService' class='org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean'>" +
|
"<b:bean id='businessService' class='org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean'>" +
|
||||||
" <b:property name='serviceUrl' value='http://localhost:8080/SomeService'/>" +
|
" <b:property name='serviceUrl' value='http://localhost:8080/SomeService'/>" +
|
||||||
" <b:property name='serviceInterface' value='org.springframework.security.annotation.BusinessService'/>" +
|
" <b:property name='serviceInterface' value='org.springframework.security.annotation.BusinessService'/>" +
|
||||||
"</b:bean>" + AUTH_PROVIDER_XML
|
"</b:bean>" + AUTH_PROVIDER_XML
|
||||||
);
|
);
|
||||||
|
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
|
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
|
||||||
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_SOMEOTHERROLE")});
|
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_SOMEOTHERROLE")});
|
||||||
SecurityContextHolder.getContext().setAuthentication(token);
|
SecurityContextHolder.getContext().setAuthentication(token);
|
||||||
target = (BusinessService) appContext.getBean("businessService");
|
target = (BusinessService) appContext.getBean("businessService");
|
||||||
target.someUserMethod1();
|
target.someUserMethod1();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContext(String context) {
|
private void setContext(String context) {
|
||||||
appContext = new InMemoryXmlApplicationContext(context);
|
appContext = new InMemoryXmlApplicationContext(context);
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,13 @@ import org.springframework.util.ClassUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a {@link ConfigAttributeDefinition} for a method or class signature.
|
* Stores a {@link ConfigAttributeDefinition} for a method or class signature.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This class is the preferred implementation of {@link MethodDefinitionSource} for XML-based
|
* 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
|
* definition of method security metadata. To assist in XML-based definition, wildcard support
|
||||||
* is provided.
|
* is provided.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
@ -51,9 +51,9 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
|
|||||||
private static final Log logger = LogFactory.getLog(MapBasedMethodDefinitionSource.class);
|
private static final Log logger = LogFactory.getLog(MapBasedMethodDefinitionSource.class);
|
||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
//~ Instance fields ================================================================================================
|
||||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||||
|
|
||||||
/** Map from RegisteredMethod to ConfigAttributeDefinition */
|
/** Map from RegisteredMethod to ConfigAttributeDefinition */
|
||||||
protected Map methodMap = new HashMap();
|
protected Map methodMap = new HashMap();
|
||||||
|
|
||||||
/** Map from RegisteredMethod to name pattern used for registration */
|
/** Map from RegisteredMethod to name pattern used for registration */
|
||||||
@ -74,48 +74,33 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
|
|||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Map.Entry entry = (Map.Entry) iterator.next();
|
Map.Entry entry = (Map.Entry) iterator.next();
|
||||||
addSecureMethod((String)entry.getKey(), (ConfigAttributeDefinition)entry.getValue());
|
addSecureMethod((String)entry.getKey(), (ConfigAttributeDefinition)entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation does not support class-level attributes.
|
* Implementation does not support class-level attributes.
|
||||||
*/
|
*/
|
||||||
protected ConfigAttributeDefinition findAttributes(Class clazz) {
|
protected ConfigAttributeDefinition findAttributes(Class clazz) {
|
||||||
return null;
|
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.
|
* Will walk the method inheritance tree to find the most specific declaration applicable.
|
||||||
*
|
|
||||||
* @param method the method to be secured
|
|
||||||
* @param attr required authorities associated with the method
|
|
||||||
*/
|
*/
|
||||||
private void addSecureMethod(RegisteredMethod method, ConfigAttributeDefinition attr) {
|
protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) {
|
||||||
Assert.notNull(method, "RegisteredMethod required");
|
return findAttributesSpecifiedAgainst(method, targetClass);
|
||||||
Assert.notNull(attr, "Configuration attribute required");
|
}
|
||||||
if (logger.isInfoEnabled()) {
|
|
||||||
logger.info("Adding secure method [" + method + "] with attributes [" + attr + "]");
|
private ConfigAttributeDefinition findAttributesSpecifiedAgainst(Method method, Class clazz) {
|
||||||
}
|
RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz);
|
||||||
this.methodMap.put(method, attr);
|
if (methodMap.containsKey(registeredMethod)) {
|
||||||
|
return (ConfigAttributeDefinition) methodMap.get(registeredMethod);
|
||||||
|
}
|
||||||
|
// Search superclass
|
||||||
|
if (clazz.getSuperclass() != null) {
|
||||||
|
return findAttributesSpecifiedAgainst(method, clazz.getSuperclass());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +111,7 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
|
|||||||
* @param attr required authorities associated with the method
|
* @param attr required authorities associated with the method
|
||||||
*/
|
*/
|
||||||
public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
|
public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
|
||||||
int lastDotIndex = name.lastIndexOf(".");
|
int lastDotIndex = name.lastIndexOf(".");
|
||||||
|
|
||||||
if (lastDotIndex == -1) {
|
if (lastDotIndex == -1) {
|
||||||
throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName");
|
throw new IllegalArgumentException("'" + name + "' is not a valid method name: format is FQN.methodName");
|
||||||
@ -134,17 +119,17 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
|
|||||||
|
|
||||||
String methodName = name.substring(lastDotIndex + 1);
|
String methodName = name.substring(lastDotIndex + 1);
|
||||||
Assert.hasText(methodName, "Method not found for '" + name + "'");
|
Assert.hasText(methodName, "Method not found for '" + name + "'");
|
||||||
|
|
||||||
String typeName = name.substring(0, lastDotIndex);
|
String typeName = name.substring(0, lastDotIndex);
|
||||||
Class type = ClassUtils.resolveClassName(typeName, this.beanClassLoader);
|
Class type = ClassUtils.resolveClassName(typeName, this.beanClassLoader);
|
||||||
|
|
||||||
addSecureMethod(type, methodName, attr);
|
addSecureMethod(type, methodName, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add configuration attributes for a secure method. Mapped method names can end or start with <code>*</code>
|
* Add configuration attributes for a secure method. Mapped method names can end or start with <code>*</code>
|
||||||
* for matching multiple methods.
|
* for matching multiple methods.
|
||||||
*
|
*
|
||||||
* @param javaType target interface or class the security configuration attribute applies to
|
* @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 mappedName mapped method name, which the javaType has declared or inherited
|
||||||
* @param attr required authorities associated with the method
|
* @param attr required authorities associated with the method
|
||||||
@ -192,6 +177,38 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds configuration attributes for a specific method, for example where the method has been
|
||||||
|
* matched using a pointcut expression. If a match already exists in the map for the method, then
|
||||||
|
* the existing match will be retained, so that if this method is called for a more general pointcut
|
||||||
|
* it will not override a more specific one which has already been added. This
|
||||||
|
*/
|
||||||
|
public void addSecureMethod(Class javaType, Method method, ConfigAttributeDefinition attr) {
|
||||||
|
RegisteredMethod key = new RegisteredMethod(method, javaType);
|
||||||
|
|
||||||
|
if (methodMap.containsKey(key)) {
|
||||||
|
logger.debug("Method [" + method + "] is already registered with attributes [" + methodMap.get(key) + "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
methodMap.put(key, attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add configuration attributes for a secure method.
|
||||||
|
*
|
||||||
|
* @param method the method to be secured
|
||||||
|
* @param attr required authorities associated with the method
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the configuration attributes explicitly defined against this bean.
|
* Obtains the configuration attributes explicitly defined against this bean.
|
||||||
*
|
*
|
||||||
@ -254,54 +271,54 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini
|
|||||||
attributes.addAll(toMerge.getConfigAttributes());
|
attributes.addAll(toMerge.getConfigAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
||||||
Assert.notNull(beanClassLoader, "Bean class loader required");
|
Assert.notNull(beanClassLoader, "Bean class loader required");
|
||||||
this.beanClassLoader = beanClassLoader;
|
this.beanClassLoader = beanClassLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return map size (for unit tests and diagnostics)
|
* @return map size (for unit tests and diagnostics)
|
||||||
*/
|
*/
|
||||||
public int getMethodMapSize() {
|
public int getMethodMapSize() {
|
||||||
return methodMap.size();
|
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 (i.e. calls super();) the registered Class
|
|
||||||
* and declaring 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");
|
* Stores both the Java Method as well as the Class we obtained the Method from. This is necessary because Method only
|
||||||
Assert.notNull(registeredJavaType, "Registered Java Type required");
|
* provides us access to the declaring class. It doesn't provide a way for us to introspect which Class the Method
|
||||||
this.method = method;
|
* was registered against. If a given Class inherits and redeclares a method (i.e. calls super();) the registered Class
|
||||||
this.registeredJavaType = registeredJavaType;
|
* and declaring 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 boolean equals(Object obj) {
|
public RegisteredMethod(Method method, Class registeredJavaType) {
|
||||||
if (this == obj) {
|
Assert.notNull(method, "Method required");
|
||||||
return true;
|
Assert.notNull(registeredJavaType, "Registered Java Type required");
|
||||||
}
|
this.method = method;
|
||||||
if (obj != null && obj instanceof RegisteredMethod) {
|
this.registeredJavaType = registeredJavaType;
|
||||||
RegisteredMethod rhs = (RegisteredMethod) obj;
|
}
|
||||||
return method.equals(rhs.method) && registeredJavaType.equals(rhs.registeredJavaType);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int hashCode() {
|
public boolean equals(Object obj) {
|
||||||
return method.hashCode() * registeredJavaType.hashCode();
|
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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "RegisteredMethod[" + registeredJavaType.getName() + "; " + method + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,25 +21,25 @@ import org.springframework.util.Assert;
|
|||||||
/**
|
/**
|
||||||
* Parses AspectJ pointcut expressions, registering methods that match the pointcut with a
|
* Parses AspectJ pointcut expressions, registering methods that match the pointcut with a
|
||||||
* traditional {@link MapBasedMethodDefinitionSource}.
|
* traditional {@link MapBasedMethodDefinitionSource}.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This class provides a convenient way of declaring a list of pointcuts, and then
|
* This class provides a convenient way of declaring a list of pointcuts, and then
|
||||||
* having every method of every bean defined in the Spring application context compared with
|
* having every method of every bean defined in the Spring application context compared with
|
||||||
* those pointcuts. Where a match is found, the matching method will be registered with the
|
* those pointcuts. Where a match is found, the matching method will be registered with the
|
||||||
* {@link MapBasedMethodDefinitionSource}.
|
* {@link MapBasedMethodDefinitionSource}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* It is very important to understand that only the <b>first</b> pointcut that matches a given
|
* It is very important to understand that only the <b>first</b> pointcut that matches a given
|
||||||
* method will be taken as authoritative for that method. This is why pointcuts should be provided
|
* method will be taken as authoritative for that method. This is why pointcuts should be provided
|
||||||
* as a <tt>LinkedHashMap</tt>, because their order is very important.
|
* as a <tt>LinkedHashMap</tt>, because their order is very important.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Note also that only beans defined in the Spring application context will be examined by this
|
* Note also that only beans defined in the Spring application context will be examined by this
|
||||||
* class.
|
* class.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Because this class registers method security metadata with {@link MapBasedMethodDefinitionSource},
|
* Because this class registers method security metadata with {@link MapBasedMethodDefinitionSource},
|
||||||
* normal Spring Security capabilities such as {@link MethodDefinitionSourceAdvisor} can be used.
|
* normal Spring Security capabilities such as {@link MethodDefinitionSourceAdvisor} can be used.
|
||||||
@ -57,18 +57,18 @@ public final class ProtectPointcutPostProcessor implements BeanPostProcessor {
|
|||||||
private static final Log logger = LogFactory.getLog(ProtectPointcutPostProcessor.class);
|
private static final Log logger = LogFactory.getLog(ProtectPointcutPostProcessor.class);
|
||||||
|
|
||||||
private Map pointcutMap = new LinkedHashMap(); /** Key: string-based pointcut, value: ConfigAttributeDefinition */
|
private Map pointcutMap = new LinkedHashMap(); /** Key: string-based pointcut, value: ConfigAttributeDefinition */
|
||||||
private MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource;
|
private MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource;
|
||||||
private PointcutParser parser;
|
private PointcutParser parser;
|
||||||
|
|
||||||
public ProtectPointcutPostProcessor(MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource) {
|
public ProtectPointcutPostProcessor(MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource) {
|
||||||
Assert.notNull(mapBasedMethodDefinitionSource, "MapBasedMethodDefinitionSource to populate is required");
|
Assert.notNull(mapBasedMethodDefinitionSource, "MapBasedMethodDefinitionSource to populate is required");
|
||||||
this.mapBasedMethodDefinitionSource = mapBasedMethodDefinitionSource;
|
this.mapBasedMethodDefinitionSource = mapBasedMethodDefinitionSource;
|
||||||
|
|
||||||
// Setup AspectJ pointcut expression parser
|
// Setup AspectJ pointcut expression parser
|
||||||
Set supportedPrimitives = new HashSet();
|
Set supportedPrimitives = new HashSet();
|
||||||
supportedPrimitives.add(PointcutPrimitive.EXECUTION);
|
supportedPrimitives.add(PointcutPrimitive.EXECUTION);
|
||||||
supportedPrimitives.add(PointcutPrimitive.ARGS);
|
supportedPrimitives.add(PointcutPrimitive.ARGS);
|
||||||
supportedPrimitives.add(PointcutPrimitive.REFERENCE);
|
supportedPrimitives.add(PointcutPrimitive.REFERENCE);
|
||||||
// supportedPrimitives.add(PointcutPrimitive.THIS);
|
// supportedPrimitives.add(PointcutPrimitive.THIS);
|
||||||
// supportedPrimitives.add(PointcutPrimitive.TARGET);
|
// supportedPrimitives.add(PointcutPrimitive.TARGET);
|
||||||
// supportedPrimitives.add(PointcutPrimitive.WITHIN);
|
// supportedPrimitives.add(PointcutPrimitive.WITHIN);
|
||||||
@ -76,79 +76,79 @@ public final class ProtectPointcutPostProcessor implements BeanPostProcessor {
|
|||||||
// supportedPrimitives.add(PointcutPrimitive.AT_WITHIN);
|
// supportedPrimitives.add(PointcutPrimitive.AT_WITHIN);
|
||||||
// supportedPrimitives.add(PointcutPrimitive.AT_ARGS);
|
// supportedPrimitives.add(PointcutPrimitive.AT_ARGS);
|
||||||
// supportedPrimitives.add(PointcutPrimitive.AT_TARGET);
|
// supportedPrimitives.add(PointcutPrimitive.AT_TARGET);
|
||||||
parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
|
parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||||
// Obtain methods for the present bean
|
// Obtain methods for the present bean
|
||||||
Method[] methods;
|
Method[] methods;
|
||||||
try {
|
try {
|
||||||
methods = bean.getClass().getMethods();
|
methods = bean.getClass().getMethods();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException(e.getMessage());
|
throw new IllegalStateException(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if any of those methods are compatible with our pointcut expressions
|
|
||||||
for (int i = 0; i < methods.length; i++) {
|
|
||||||
Iterator iter = pointcutMap.keySet().iterator();
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
String ex = iter.next().toString();
|
|
||||||
|
|
||||||
// Parse the presented AspectJ pointcut expression
|
|
||||||
PointcutExpression expression = parser.parsePointcutExpression(ex);
|
|
||||||
|
|
||||||
// Try for the bean class directly
|
// Check to see if any of those methods are compatible with our pointcut expressions
|
||||||
if (attemptMatch(bean.getClass(), methods[i], expression, beanName)) {
|
for (int i = 0; i < methods.length; i++) {
|
||||||
// We've found the first expression that matches this method, so move onto the next method now
|
Iterator iter = pointcutMap.keySet().iterator();
|
||||||
break; // the "while" loop, not the "for" loop
|
while (iter.hasNext()) {
|
||||||
}
|
String ex = iter.next().toString();
|
||||||
}
|
|
||||||
}
|
// Parse the presented AspectJ pointcut expression
|
||||||
|
PointcutExpression expression = parser.parsePointcutExpression(ex);
|
||||||
return bean;
|
|
||||||
}
|
// Try for the bean class directly
|
||||||
|
if (attemptMatch(bean.getClass(), methods[i], expression, beanName)) {
|
||||||
private boolean attemptMatch(Class targetClass, Method method, PointcutExpression expression, String beanName) {
|
// We've found the first expression that matches this method, so move onto the next method now
|
||||||
// Determine if the presented AspectJ pointcut expression matches this method
|
break; // the "while" loop, not the "for" loop
|
||||||
boolean matches = expression.matchesMethodExecution(method).alwaysMatches();
|
}
|
||||||
|
}
|
||||||
// Handle accordingly
|
}
|
||||||
if (matches) {
|
|
||||||
ConfigAttributeDefinition attr = (ConfigAttributeDefinition) pointcutMap.get(expression.getPointcutExpression());
|
return bean;
|
||||||
|
}
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("AspectJ pointcut expression '" + expression.getPointcutExpression() + "' matches target class '" + targetClass.getName() + "' (bean ID '" + beanName + "') for method '" + method + "'; registering security configuration attribute '" + attr + "'");
|
private boolean attemptMatch(Class targetClass, Method method, PointcutExpression expression, String beanName) {
|
||||||
}
|
// Determine if the presented AspectJ pointcut expression matches this method
|
||||||
|
boolean matches = expression.matchesMethodExecution(method).alwaysMatches();
|
||||||
mapBasedMethodDefinitionSource.addSecureMethod(targetClass, method.getName(), attr);
|
|
||||||
}
|
// Handle accordingly
|
||||||
|
if (matches) {
|
||||||
return matches;
|
ConfigAttributeDefinition attr = (ConfigAttributeDefinition) pointcutMap.get(expression.getPointcutExpression());
|
||||||
}
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
public void setPointcutMap(Map map) {
|
logger.debug("AspectJ pointcut expression '" + expression.getPointcutExpression() + "' matches target class '" + targetClass.getName() + "' (bean ID '" + beanName + "') for method '" + method + "'; registering security configuration attribute '" + attr + "'");
|
||||||
Assert.notEmpty(map);
|
}
|
||||||
Iterator i = map.keySet().iterator();
|
|
||||||
while (i.hasNext()) {
|
mapBasedMethodDefinitionSource.addSecureMethod(targetClass, method, attr);
|
||||||
String expression = i.next().toString();
|
}
|
||||||
Object value = map.get(expression);
|
|
||||||
Assert.isInstanceOf(ConfigAttributeDefinition.class, value, "Map keys must be instances of ConfigAttributeDefinition");
|
return matches;
|
||||||
addPointcut(expression, (ConfigAttributeDefinition) value);
|
}
|
||||||
}
|
|
||||||
}
|
public void setPointcutMap(Map map) {
|
||||||
|
Assert.notEmpty(map);
|
||||||
|
Iterator i = map.keySet().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
String expression = i.next().toString();
|
||||||
|
Object value = map.get(expression);
|
||||||
|
Assert.isInstanceOf(ConfigAttributeDefinition.class, value, "Map keys must be instances of ConfigAttributeDefinition");
|
||||||
|
addPointcut(expression, (ConfigAttributeDefinition) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPointcut(String pointcutExpression, ConfigAttributeDefinition definition) {
|
||||||
|
Assert.hasText(pointcutExpression, "An AspectJ pointcut expression is required");
|
||||||
|
Assert.notNull(definition, "ConfigAttributeDefinition required");
|
||||||
|
pointcutMap.put(pointcutExpression, definition);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("AspectJ pointcut expression '" + pointcutExpression + "' registered for security configuration attribute '" + definition + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addPointcut(String pointcutExpression, ConfigAttributeDefinition definition) {
|
|
||||||
Assert.hasText(pointcutExpression, "An AspectJ pointcut expression is required");
|
|
||||||
Assert.notNull(definition, "ConfigAttributeDefinition required");
|
|
||||||
pointcutMap.put(pointcutExpression, definition);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("AspectJ pointcut expression '" + pointcutExpression + "' registered for security configuration attribute '" + definition + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,8 @@ public abstract class ConfigTestUtils {
|
|||||||
" <user-service id='us'>" +
|
" <user-service id='us'>" +
|
||||||
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
|
||||||
" <user name='bill' password='billspassword' authorities='ROLE_A,ROLE_B,AUTH_OTHER' />" +
|
" <user name='bill' password='billspassword' authorities='ROLE_A,ROLE_B,AUTH_OTHER' />" +
|
||||||
|
" <user name='admin' password='password' authorities='ROLE_ADMIN,ROLE_USER' />" +
|
||||||
|
" <user name='user' password='password' authorities='ROLE_USER' />" +
|
||||||
" </user-service>" +
|
" </user-service>" +
|
||||||
" </authentication-provider>";
|
" </authentication-provider>";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.springframework.security.intercept.method;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.ConfigAttributeDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link MapBasedMethodDefinitionSource}.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @since 2.0.4
|
||||||
|
*/
|
||||||
|
public class MapBasedMethodDefinitionSourceTests {
|
||||||
|
private final ConfigAttributeDefinition ROLE_A = new ConfigAttributeDefinition("ROLE_A");
|
||||||
|
private final ConfigAttributeDefinition ROLE_B = new ConfigAttributeDefinition("ROLE_B");
|
||||||
|
private MapBasedMethodDefinitionSource mds;
|
||||||
|
private Method someMethodString;
|
||||||
|
private Method someMethodInteger;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void initialize() throws Exception {
|
||||||
|
mds = new MapBasedMethodDefinitionSource();
|
||||||
|
someMethodString = MockService.class.getMethod("someMethod", String.class);
|
||||||
|
someMethodInteger = MockService.class.getMethod("someMethod", Integer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void wildcardedMatchIsOverwrittenByMoreSpecificMatch() {
|
||||||
|
mds.addSecureMethod(MockService.class, "some*", ROLE_A);
|
||||||
|
mds.addSecureMethod(MockService.class, "someMethod*", ROLE_B);
|
||||||
|
assertEquals(ROLE_B, mds.getAttributes(someMethodInteger, MockService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodsWithDifferentArgumentsAreMatchedCorrectly() throws Exception {
|
||||||
|
mds.addSecureMethod(MockService.class, someMethodInteger, ROLE_A);
|
||||||
|
mds.addSecureMethod(MockService.class, someMethodString, ROLE_B);
|
||||||
|
|
||||||
|
assertEquals(ROLE_A, mds.getAttributes(someMethodInteger, MockService.class));
|
||||||
|
assertEquals(ROLE_B, mds.getAttributes(someMethodString, MockService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockService {
|
||||||
|
public void someMethod(String s) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void someMethod(Integer i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user