Object based security using Spring Security ACL in Grails
Spring security is used to secure the URL and only authenticated user is allowed to access that. But sometimes we need to secure the java Class instances and here comes ACL to rescue us.
Let’s see why we need object based security/ACL model.
Suppose, there is relation “BankAccount” and 10 records exists for it. So as per the requirement, only authorized person should have access to his/her account i.e only BankAccount holder or some bank authorities should have access over it and not any other have.
Now, depending on the User’s role we can restrict only the URLs using Spring Security core but not the access to any record. To achieve the object based security we need Spring Security ACL. Spring Security ACL uses Spring Security Core API.
You can use Spring Security ACL in any Java web application, for configuring the same in Grails App you can follow the link .
After successful configuration there will be new tables will be created
-
acl_class
-
acl_entry
-
acl_object_identity
-
acl_sid
Note: This is tested till Grails version 2.3.7 and I am not going to explain tables that were created due to Spring Security Core api (ie: user, role, user_role etc)
Note: Spring ACL security framework provides some beans(or say grails services) that ease our task like aclService, aclUtilService, permissionEvaluator
The whole ACL functionality designed to work around above 4 tables.
-
acl_class
Object class that need to be secured using object based security (for example Account domain discussed in above example) should have an entry in this table
aclService.createAcl(domainObject), per domain class only one entry is required in this table
-
acl_sid
This table contains the entry for user or role to whom we want to provide permission on any object instance. We don’t need to maintain this table at all, when we add any permission to any user, entry is automatically handled by AclUtilService.
-
acl_object_identity
For each secured instance of object there should be an entry in this table. This entry is also maintained by AclUtilService when we add a permission to any object instance.
-
acl_entry
Whenever we add or remove a permission to/from an object instance for a user/role, the entry for each permission (read,write,create,administration) is maintained in this table.
aclUtilService.addPermission(domainObject, User.findByName('xyz'), BasePermission.ADMINISTRATION)
Once we have done with providing permission to an object, now its time to secure our operation over it
@PostFilter("hasPermission(filterObject, 'administration') or hasPermission(filterObject, 'read')")
List<Account> listAccounts(){
Account.list()
}
In above code we have used PostFilter annotation, that processes the returning object list, and only returns the authorized list of objects for logged in user(objects on which user have either administration or read rights) .
@PreFilter("hasPermission(filterObject, 'read')")
Map fetchAmountInAccounts(List<Account> accounts){
Map map=[:];
accounts.each{
map.put(it,it.amount);
}
map
}
In above code only authorized objects will be provided to the function for any operation from the supplied list of objects.
We can also restrict some operation on any object using PreAuthorize annotation,
@PreAuthorize("hasPermission(#account,'admin')")
void deleteAccount(Account account, OtherInfo info){}
Here if logged-in user doesn’t have admin permission on supplied account instance, he will not be able to perform operation over it. @PreAuthorize decides if a method can be invoked or not.
We can also apply access control check after method execution using PostAuthorize annotation,
@PostAuthorize("hasPermission(returnObject,'read')")
Account searchAccount(FilterParams params){
//search activity
account
}
above code ensures that only authorized user will be able to see the account details.
PermissionEvaluator interface provides a way to check if some user have particular permission on any object instance or not,
permissionEvaluator.hasPermission(authentication, domainObject, BasePermission.READ)
returns true/false stating if provided user have permission on given domainObject or not.
Here is a sample application to test the Spring Security ACL.
May be required:
We found an issue in Spring Security ACL grails plugin (spring-security-acl:2.0-RC1), to handle it create a class as below
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil
import org.springframework.security.acls.domain.ObjectIdentityImpl
import org.springframework.security.acls.model.ObjectIdentity
import org.springframework.security.acls.model.ObjectIdentityGenerator
import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy
class MyObjectIdentityRetrievalStrategy implements ObjectIdentityRetrievalStrategy, ObjectIdentityGenerator {
ObjectIdentity getObjectIdentity(domainObject) {
domainObject = GrailsHibernateUtil.unwrapIfProxy(domainObject)
String className = domainObject.getClass().name
createObjectIdentity(domainObject.id, className)
}
ObjectIdentity createObjectIdentity(Serializable id, String type) {
new ObjectIdentityImpl(type, id)
}
}
And make an entry in spring\resources.groovy class inside beans closer
objectIdentityRetrievalStrategy(MyObjectIdentityRetrievalStrategy)