I use the Spring Security 3.1 ACL implementation. So based on a tutorial i have created a acl databse with the following tables:
CREATE TABLE IF NOT EXISTS `acl_class` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`class` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_uk_2` (`class`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `acl_entry` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`acl_object_identity` bigint(20) NOT NULL,
`ace_order` int(11) NOT NULL,
`sid` bigint(20) NOT NULL,
`mask` int(11) NOT NULL,
`granting` tinyint(1) NOT NULL,
`audit_success` tinyint(1) NOT NULL,
`audit_failure` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_uk_4` (`acl_object_identity`,`ace_order`),
KEY `foreign_fk_5` (`sid`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `acl_object_identity` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`object_id_class` bigint(20) NOT NULL,
`object_id_identity` bigint(20) NOT NULL,
`parent_object` bigint(20) DEFAULT NULL,
`owner_sid` bigint(20) DEFAULT NULL,
`entries_inheriting` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_uk_3` (`object_id_class`,`object_id_identity`),
KEY `foreign_fk_1` (`parent_object`),
KEY `foreign_fk_3` (`owner_sid`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `acl_sid` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`principal` tinyint(1) NOT NULL,
`sid` varchar(100) NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
This works fine with Anntotations like:
@PreAuthorize("hasPermission(#element, 'WRITE')")
@PostAuthorize("hasPermission(returnObject, 'READ')")
The rights "Read" and "Write" are set in the table acl_entry to the field mask. As i understood 1 means "READ", 2 means "Write", 4 means "Create", 8 means "Delete" and 16 means "Administer", because it seems to be a bitwise authentication method.
Now i have to create single entries for the "READ" and "Write" permission, that's not pretty handy.
According to Spring Security 3.1 by PacktPub:
Unfortunately, the actual implementation ofAclImpl
directly compares the permission specified in our SpEL expression in our [@PostFilter
] annotation, and the permission stored on the ACE in the database, without using bitwise logic. The Spring Security community is in debate about whether this is unintentional or working as intended. . .
The example in that book tries to do exactly what you're describing -- it specifies a user with a role of 3 for read/write, but the user is denied access to an object with a permission mask of 1 for read.
The solution is to write your own custom permission evaluator.
MyPermissionEvaluator.java:
public class MyPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object requiredPermissions) {
//some way to access your user's assigned permission mask
int permissionMask = MyUserDetails.getMask();
//the requiredPermissions object must be cast as a String, and then
//converted to an integer, even though it is an integer in the ACL table
int permissionsRequired = Integer.valueOf(requiredPermissions.toString());
//a simple bitwise OR will identify whether the user has the required permissions
return ((permissionMask | permissionsRequired) == permissionMask);
}
. . .
}
To actually use this custom permission evaluator, edit your security.xml
file:
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<bean id="espressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="permissionEvaluator"/>
</bean>
<bean id="permissionEvaluator" class="my.project.package.MyPermissionEvaluator"/>
Finally, whenever a method or class requires a certain permission level:
@PreAuthorize("hasPermission(#this, '4')")
public void mySecuredMethod() { //some secured method
}
Now you can set the permission mask in the ACL table to whatever corresponds to your organizational needs (bitwise), and do the same in whatever way you identify each individual user's permissions. For example,
user site_admin_bit database_admin_bit edit_data_bit write_data_bit read_data_bit
nancy 0 1 1 0 1
Nancy thus has a permission mask of 13 (out of a possible 31) as stored in your user details implementation. If she tries to access an object with a permission requirement of edit_data
, her permissions would be checked against a mask requirement of 4, and a bitwise OR evaluation (permissionMask | permissionsRequired == permissionMask
) would evaluate to true
.
This is, in my estimation, the easiest way to implement an organization-specific bitwise permissions mask (with 32 bits to play with, which should be enough, I should think). According to the referenced book, the hasPermission
SpEL expression used in Spring annotations evaluates the user's permissions as a complete unit; if the user has a permission set at 3 for read/write, but the annotation only evaluates against read (1), the user will be denied access.
From SpringSecurity
As mentioned in the last paragraph, the ACL system uses integer bit masking. Don't worry, you need not be aware of the finer points of bit shifting to use the ACL system, but suffice to say that we have 32 bits we can switch on or off. Each of these bits represents a permission, and by default the permissions are read (bit 0), write (bit 1), create (bit 2), delete (bit 3) and administer (bit 4). It's easy to implement your own Permission instance if you wish to use other permissions, and the remainder of the ACL framework will operate without knowledge of your extensions.
@question#1: yes that's right.
@question#2: you could use something like:
new BasePermission(BasePermission.WRITE.getMask() | BasePermission.READ.getMask())
to get a READ
and WRITE
permission.
From the spring docs:
// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
//Permission p = BasePermission.ADMINISTRATION;
Permission p = new BasePermission(BasePermission.WRITE.getMask() | BasePermission.READ.getMask());
// Create or update the relevant ACL
MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
acl = aclService.createAcl(oi);
}
// Now grant some permissions via an access control entry (ACE)
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);
In order to implement a bit-wise permission evaluation, instead of implementing a PermissionEvaluator, which can be quite difficult, you can override the DefaultPermissionGrantingStrategy with your own one.
This could be your Spring ACL configuration
1) Your ACL Service
<bean class="org.springframework.security.acls.jdbc.JdbcMutableAclService" id="aclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="lookupStrategy"/>
<constructor-arg ref="aclCache"/>
</bean>
2) Your lookup strategy (dataSource, aclCache and aclAuthorizationStrategy are the default ones)
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg ref="aclAuthorizationStrategy"/>
<constructor-arg ref="permissionGrantingStrategy"/>
</bean>
3) Here comes the interesting part, the PermissionGrantingStrategy (http://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/acls/model/PermissionGrantingStrategy.html)
<bean id="permissionGrantingStrategy" class="com.example.MyPermissionGrantingStrategy"/>
Here is where you would implement the bit-wise permission logic, and then override the Spring's default one.
I hope this can help
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With