Acegi-definitions for security

To hook Acegi to the secure objects, some beans have to be defined. The bean-definitions are added to 'src/main/webapp/WEB-INF/security.xml'.

Secure PersonManager

The first bean to define is a secure instance of the PersonManager, which gets secured by the acegi ACLs:

<bean id="personManagerSecure" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="proxyInterfaces"><value>org.appfuse.service.PersonManager</value></property>
	<property name="interceptorNames">
		<list>
			<idref bean="personSecurity"/>
			<idref bean="personManager"/>
		</list>
	</property>
</bean>

Definition of an interceptor

Next, an interceptor gets defined, which interecepts calls to the new secure PersonManager and probably intervents:

<bean id="personSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
	<property name="authenticationManager"><ref bean="authenticationManager"/></property>
	<property name="accessDecisionManager"><ref bean="personAccessDecisionManager"/></property>
	<property name="afterInvocationManager"><ref bean="afterInvocationManager"/></property>
	<property name="objectDefinitionSource">
		<value>
			org.appfuse.service.PersonManager.getPerson*=ROLE_USER,ROLE_ADMIN,AFTER_ACL_READ
			org.appfuse.service.PersonManager.savePerson*=ACL_PERSON_WRITE
			org.appfuse.service.PersonManager.removePerson*=ACL_PERSON_DELETE,ROLE_ADMIN,ROLE_USER
			org.appfuse.service.PersonManager.getPersons*=ACL_PERSON_READ,AFTER_ACL_COLLECTION_READ
		</value>
	</property>
</bean>
  • The property 'objectDefinitionSource' configures, how the methods should be secured.
  • One possibility for this are roles (ROLE_USER, ROLE_ADMIN)
  • Additionally the user's rights for access to a single object can be checked (bspw. ACL_PERSON_READ oder ACL_PERSON_DELETE)
  • Finally a ACL-based filter can be added for all methods returning Person-objects.
    • AFTER_ACL_READ is for methods that return a single Person-object.
    • AFTER_ACL_COLLECTION_READ is for methods, that return a collection of Persons.

Define a DecisionVoter

Now all the voters, that vote for or against a single access get defined.

Voter for Persons

This is the definition of a voter for Persons. This voter will get assigned other voters, that vote for or against the access to an object, so finally a decision can be made:

<!-- An access decision manager used by the business objects -->
<bean id="personAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
	<property name="allowIfAllAbstainDecisions"><value>false</value></property>
	<property name="decisionVoters">
		<list>
			<bean class="org.acegisecurity.vote.RoleVoter"/>
			<ref local="aclPersonReadVoter"/>
			<ref local="aclPersonDeleteVoter"/>
			<ref local="aclPersonWriteVoter"/>
			<ref local="aclPersonAdminVoter"/>
		</list>
	</property>
</bean>

The single voters now get defined in the following sections.

Class definition of a Voter

For the reason of the standard voter from Acegi (org.acegisecurity.vote.BasicAclEntryVoter) only being able to handle mthods, that have a secured object as a parameter, we now have to implement a new voter for each domain-object that shall get secured. This voter will also be capable of returning the right domain instance for other methods (i.e. like getPersons()).

package org.appfuse.service.voter;
 
import org.acegisecurity.vote.BasicAclEntryVoter;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
 
import org.appfuse.model.Person;
 
/**
 * Modified BasicAclEntryVoter, which allows securing of
 * methods with no parameters.
 * 
 * @author Tobias Vogel
 *
 */
public class PersonAclVoter extends BasicAclEntryVoter {
 
	private Person person = new Person();
 
	@SuppressWarnings("unchecked")
	@Override
    protected Object getDomainObjectInstance(Object secureObject) {
        Object[] args;
        Class[] params;
 
        if (secureObject instanceof MethodInvocation) {
            MethodInvocation invocation = (MethodInvocation) secureObject;
            params = invocation.getMethod().getParameterTypes();
            args = invocation.getArguments();
        } else {
            JoinPoint jp = (JoinPoint) secureObject;
            params = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
            args = jp.getArgs();
        }
 
        for (int i = 0; i < params.length; i++) {
            if (getProcessDomainObjectClass().isAssignableFrom(params[i])) {
                return args[i];
            }
        }
 
        // return a new empty person for other methods 
        return person;
    }
 
}

Here you can see, that a new, empty Person object gets returned. Later on, when creating an object identity solely 'org.appfuse.model.Person' will be returned, without an ID separated by a colon. This now also explains, why one can find such an entry in the sample data - it defines the access rights for new person objects.

Voter for read access

<!-- An access decision voter that reads ACL_PERSON_READ configuration settings -->
<bean id="aclPersonReadVoter" class="org.appfuse.service.voter.PersonAclVoter">
	<property name="processConfigAttribute"><value>ACL_PERSON_READ</value></property>
	<property name="processDomainObjectClass"><value>org.appfuse.model.Person</value></property>
	<property name="aclManager"><ref bean="aclManager"/></property>
	<property name="requirePermission">
		<list>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
		</list>
	</property>
</bean>

Voter for deletions

<!-- An access decision voter that reads ACL_PERSON_DELETE configuration settings -->
<bean id="aclPersonDeleteVoter" class="org.appfuse.service.voter.PersonAclVoter">
	<property name="processConfigAttribute"><value>ACL_PERSON_DELETE</value></property>
	<property name="processDomainObjectClass"><value>org.appfuse.model.Person</value></property>
	<property name="aclManager"><ref bean="aclManager"/></property>
	<property name="requirePermission">
		<list>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.DELETE"/>
		</list>
	</property>
</bean>

Voter für write access

<!-- An access decision voter that reads ACL_PERSON_DELETE configuration settings -->
<bean id="aclPersonWriteVoter" class="org.appfuse.service.voter.PersonAclVoter">
	<property name="processConfigAttribute"><value>ACL_PERSON_WRITE</value></property>
	<property name="processDomainObjectClass"><value>org.appfuse.model.Person</value></property>
	<property name="aclManager"><ref bean="aclManager"/></property>
	<property name="requirePermission">
		<list>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.WRITE"/>
		</list>
	</property>
</bean>

Voter for modification of ACLs

<!-- An access decision voter that reads ACL_PERSON_ADMIN configuration settings -->
<bean id="aclPersonAdminVoter" class="org.appfuse.service.voter.PersonAclVoter">
	<property name="processConfigAttribute"><value>ACL_PERSON_ADMIN</value></property>
	<property name="processDomainObjectClass"><value>org.appfuse.model.Person</value></property>
	<property name="aclManager"><ref bean="aclManager"/></property>
	<property name="requirePermission">
		<list>
			<ref bean="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
		</list>
	</property>
</bean>

Definition of ACL-values

The ACL-permissions used in our application now also need to be defined:

<!-- ACL permission masks used by this application -->
<bean id="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION</value></property>
</bean>
<bean id="org.acegisecurity.acl.basic.SimpleAclEntry.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.READ</value></property>
</bean>
<bean id="org.acegisecurity.acl.basic.SimpleAclEntry.DELETE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.DELETE</value></property>
</bean>
<bean id="org.acegisecurity.acl.basic.SimpleAclEntry.WRITE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.WRITE</value></property>
</bean>

Definition of an ACL-manager

An ACL-manager is responsible for reading the ACLs for a single domain object:

<bean id="aclManager" class="org.acegisecurity.acl.AclProviderManager">
	<property name="providers">
		<list>
			<ref bean="basicAclProviderManager"/>
		</list>
	</property>
</bean>

Definition of an afterInvocationManager

The afterInvocationManager is responsible for only letting a method return objects, the calling user has adequate permissions for.

<bean id="afterInvocationManager" class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">
	<property name="providers">
		<list>
			<ref local="afterAclRead"/>
			<ref local="afterAclCollectionRead"/>
		</list>
	</property>
</bean>
 
<!-- Processes AFTER_ACL_READ configuration settings -->
<bean id="afterAclRead" class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationProvider">
	<property name="aclManager"><ref local="aclManager"/></property>
	<property name="requirePermission">
		<list>
			<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
			<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
		</list>
	</property>
</bean>
 
<!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
<bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
	<property name="aclManager"><ref local="aclManager"/></property>
	<property name="requirePermission">
		<list>
			<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
			<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
		</list>
	</property>
</bean>

Further thoughts

The definitions needed to secure a single manager are quite extensive. For this reason it makes sense, to create a new config file for each manager to secure, and just place an import statement in your security.xml file.

Recent changes RSS feed Creative Commons License Donate Driven by DokuWiki