OK - I've tested this using a ClassPathXmlApplicationContext and it works.
Code:
<bean
id="authorizationServiceImpl"
class="com.example.security.authorization.impl.AuthorizationServiceImpl">
<property
name="methodInvocationPrivilegeEvaluator"
ref="methodInvocationPrivilegeEvaluator"/>
<property
name="secureObjectIDs">
<set>
<value>needsAuthorizedMethod</value>
</set>
</property>
</bean>
<bean
id="needsAuthorizedMethod"
class="com.example.protect.NeedsAuthorizedMethod">
<security:intercept-methods>
<security:protect method="returnString" access="ROLE_ADMIN" />
</security:intercept-methods>
</bean>
Code:
private Set<String> secureObjectIDs;
public Set<String> getSecureObjectIDs()
{
return secureObjectIDs;
}
public void setSecureObjectIDs(Set<String> secureObjectIDs)
{
this.secureObjectIDs = secureObjectIDs;
}
public static String METHOD_SECURITY_INTERCEPTOR_ID_POSTFIX =
"methodSecurityInterceptor";
private Map<Object, MethodSecurityInterceptor> objectToSecureMethodInterceptorMap;
public void initializeObjectToSecureMethodInterceptorMap(BeanFactory beanFactory)
{
Assert.notNull(this.secureObjectIDs, "In order to use the service configure at least one secure object");
objectToSecureMethodInterceptorMap = new HashMap();
for (String secureObjectID : secureObjectIDs)
{
String methodSecurityInterceptorID =
secureObjectID
+
"."
+
METHOD_SECURITY_INTERCEPTOR_ID_POSTFIX;
Object object = beanFactory.getBean(secureObjectID);
//TODO Test what happens when the methodsecurityinterceptor is not present...
//In other words we listed an ID that does not have any secure methods
MethodSecurityInterceptor methodSecurityInterceptor =
(MethodSecurityInterceptor)
beanFactory.
getBean(methodSecurityInterceptorID);
if (object != null && methodSecurityInterceptor != null)
{
objectToSecureMethodInterceptorMap.put(object, methodSecurityInterceptor);
}
}
}
Code:
public boolean isCallable(Object object, String methodName) throws Exception
{
Authentication authentication =
SecurityContextHolder.
getContext().
getAuthentication();
Assert.notNull(object, "DWR remoted Spring bean instance required");
Assert.notNull(this.objectToSecureMethodInterceptorMap, "The map can't be null");
MethodSecurityInterceptor methodSecurityInterceptor =
this.objectToSecureMethodInterceptorMap.get(object);
Assert.notNull(methodSecurityInterceptor, "Could not find MethodSecurityInterceptor instance per the given object key.");
if (methodSecurityInterceptor == null)
{
return false;
}
MethodInvocation methodInvocation = null;
for(Method method: object.getClass().getMethods())
{
if (method.getName().equals(methodName))
{
methodInvocation = new SimpleMethodInvocation(object, method,null);
}
}
this.
getMethodInvocationPrivilegeEvaluator().
setSecurityInterceptor(methodSecurityInterceptor);
return this.getMethodInvocationPrivilegeEvaluator().
isAllowed(
methodInvocation,
authentication);
}
In the unit tests I pass the ClassPathXmlApplicationContext instance as the BeanFactory that's necessary to initialize the objectToSecureMethodInterceptorMap reference. So this needs some tweaking in order to make it work in a web context. I think all that's necessary is to lookup the factory like this:
ContextLoader.getCurrentWebApplicationContext()
I'm going to check that next.