My solution is a simple proxy which implements PermissionEvaluator and ApplicationContextAware. It has a string property proxiedBeanName for the name of the custom permission evaluator bean. Like this, the custom permission evaluator can be built like any other regular bean, including autowired dependencies. The link to the permission system whose referenced beans seem not to undergo the same bean life cycle is made deferred at the first call to the proxied bean by calling the getDelegate() method.
Code:
package my.foo.bar;
import java.io.Serializable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.Authentication;
/**
* Loose coupling between the Spring {@link SecurityExpressionHandler} and an associated {@link PermissionEvaluator}.
* Reason: a permission evaluator does not undergo the same life cycle as the other beans and autowiring repositories is not
* possible in dependent beans without using this proxy.
* It delays reference of a custom {@link PermissionEvaluator} to the first execution, so the custom bean can be build independently by spring.
* @author till
*
*/
public class PermissionEvaluatorProxy implements PermissionEvaluator, ApplicationContextAware {
private ApplicationContext applicationContext;
private String proxiedBeanName;
private PermissionEvaluator delegate = null;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return this.getDelegate().hasPermission(authentication,targetDomainObject, permission);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return this.getDelegate().hasPermission(authentication, targetId, targetType, permission);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private synchronized PermissionEvaluator getDelegate() {
if (this.delegate == null) {
// use the application context to load the bean explicitly by its name
this.delegate = (PermissionEvaluator)this.applicationContext.getBean(this.proxiedBeanName);
}
return this.delegate;
}
/**
* @param delegateName the name of the referenced/proxied bean.
*/
@Required
public void setProxiedBeanName(String delegateBeanName) {
this.proxiedBeanName = delegateBeanName;
}
}
in the spring context:
Code:
<security:global-method-security pre-post-annotations="enabled" >
<security:expression-handler ref="methodSecurityExpressionHandler" />
</security:global-method-security>
<bean id="methodSecurityExpressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean class="my.foo.bar.PermissionEvaluatorProxy">
<property name="proxiedBeanName" value="myCustomPermissionEvaluator"/>
</bean>
</property>
</bean>
<bean id="myCustomPermissionEvaluator" class="my.foo.bar.MyClassImplementingPermissionEvaluator"/>
good luck.