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.