Here's a sample screenshot:
This tutorial assumes you're familiar with setting-up a basic intercept-url based Spring Security application, and Spring MVC application. If you need a review, please read my other tutorials first regarding these topics.
What is Expression-Based Access Control?
Spring Security 3.0 introduced the ability to use Spring EL expressions as an authorization mechanism in addition to the simple use of configuration attributes and access-decision voters which have seen before. Expression-based access control is built on the same architecture but allows complicated boolean logic to be encapsulated in a single expression
Source: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html
What is ACL?
Complex applications often will find the need to define access permissions not simply at a web request or method invocation level. Instead, security decisions need to comprise both who (Authentication), where (MethodInvocation) and what (SomeDomainObject). In other words, authorization decisions also need to consider the actual domain object instance subject of a method invocation.
Source: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/domain-acls.html
An access control list (ACL), with respect to a computer file system, is a list of permissions attached to an object. An ACL specifies which users or system processes are granted access to objects, as well as what operations are allowed on given objects. Each entry in a typical ACL specifies a subject and an operation. For instance, if a file has an ACL that contains (Alice, delete), this would give Alice permission to delete the file.
Source: http://en.wikipedia.org/wiki/Access_control_list
Functional Specs
To understand more about ACL, let's compare it against a simple Spring Security intercept-url based configuration. If we have three sections, Admin Posts, Personal Posts, Visitors Posts, we'll probably pattern our configuration similar to the following:<security:intercept-url pattern="/krams/admin" access="hasRole('ROLE_ADMIN')"/> <security:intercept-url pattern="/krams/personal" access="hasRole('ROLE_USER')"/> <security:intercept-url pattern="/krams/public" access="hasRole('ROLE_VISITOR')"/>In this setup, here's what we can observe:
- Only admins can access Admin Posts
- Only users can access Personal Posts
- Only visitors can access Visitors Posts
An admin has READ and WRITE access to everything, but only READ access to the Personal Posts. See the table below:
Admin
Post Type | View | Add | Edit | Delete |
Admin | x | x | x | x |
Personal | x | |||
Public | x | x | x | x |
A regular user has READ and WRITE access to Personal Posts and Public Posts but only READ access to Admin Posts. See the table below:
User
Post Type | View | Add | Edit | Delete |
Admin | ||||
Personal | x | x | x | x |
Public | x | x | x | x |
A visitor can only read Admin and Public Posts but no access of whatsoever in the Personal Posts section. See the table below:
Visitor
Post Type | View | Add | Edit | Delete |
Admin | ||||
Personal | ||||
Public | x |
The Difficulty
To do this using intercept-url you will have to do the following:Admin Posts
<security:intercept-url pattern="/krams/admin/view" access="hasRole('ROLE_ADMIN')"/> <security:intercept-url pattern="/krams/admin/add" access="hasRole('ROLE_ADMIN')"/> <security:intercept-url pattern="/krams/admin/edit" access="hasRole('ROLE_ADMIN')"/> <security:intercept-url pattern="/krams/admin/delete" access="hasRole('ROLE_ADMIN')"/>
Personal Posts
<security:intercept-url pattern="/krams/personal/view" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')"/> <security:intercept-url pattern="/krams/personal/add" access="hasRole('ROLE_USER')"/> <security:intercept-url pattern="/krams/personal/edit" access="hasRole('ROLE_USER')"/> <security:intercept-url pattern="/krams/personal/delete" access="hasRole('ROLE_USER')"/>
Public Posts
<security:intercept-url pattern="/krams/public/view" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER') or hasRole('ROLE_VISITOR')"/> <security:intercept-url pattern="/krams/public/add" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')"/> <security:intercept-url pattern="/krams/public/edit" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')"/> <security:intercept-url pattern="/krams/public/delete" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')"/>This setup works. However there are some problems that we observe:
1. It only works at the Controller level--that is at the URL level.
2. What happens if we have a domain object that doesn't correspond to a particular URL?
3. What if we need to display the same URL that contains our three domain objects together: Admin, Personal, and Public Posts? It's either we get an Access Denied or we see everything.
The Solution
We use ACL. The idea is we put the restriction on the domain object itself. It's similar to the way we access files in the computer. Various users have different READ and WRITE access to certain files. Some have READ access, but some have both READ and WRITE access.The Application
We'll begin our application by configuring the required Spring Security configuration and the required custom classes. Then in Part 2, we'll build the Spring MVC section.In Spring Security 3, there's the heavyweight solution of implementing ACL (See Spring Security 3 Reference Chapter 16). And there's also the lightweight solution of implementing ACL through the use of Expression-Based Access Control
To use it, we need to mark our methods with Method Security Expressions (see Chapter 15.3). They are @PreAuthorize, @PreFilter, @PostAuthorize and @PostFilter.
Then we need to use the hasPermission() expression inside these Method Security Expressions. For example:
@PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean add(PublicPost post) { ... }
How do we enable hasPermission() and where does it get its permissions?
To use hasPermission() expressions, you have to explicitly configure a PermissionEvaluator
Source: Spring Security 3 Reference 15.3.2 Built-In Expressions
The PermissionEvaluator can be a custom implementation or a default Spring Security implementation. For this tutorial, we will do a custom implementation.
Why custom implementation?
Because we want to stay away from the complexity of setting up an ACL database and to make this tutorial simple to learn. Also, the strength of the PermissionEvaluator interface and Expression-Based Access Control is it allows us to construct our own implementation. And since it's our own implementation, we know how it works.
The Custom Map
We'll declare a simple Map that contains a list of ROLES and OBJECTS. Whenever a user tries to access a protected object, it will be checked against this Map. For each ROLE, we assigned what objects it can owned and what permissions it can have.We have three domain objects that correspond to an actual object in the application.
org.krams.tutorial.domain.AdminPost org.krams.tutorial.domain.PersonalPost org.krams.tutorial.domain.PublicPostWe also have three roles that correspond to the roles declared in the authentication-manager
ROLE_ADMIN ROLE_USER ROLE_VISITORHere's the configuration. Notice we created a new XML configuration to isolate ACL-related configurations.
acl-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <!-- Declare a simple map containing all our roles --> <util:map id="permissionsMap"> <entry key="ROLE_ADMIN" value-ref="admin"/> <entry key="ROLE_USER" value-ref="user"/> <entry key="ROLE_VISITOR" value-ref="visitor"/> </util:map> <!-- Declare permissions for Admin Contains a map of objects and their associated allowed actions --> <bean id="admin" class="org.krams.tutorial.security.Permission" > <property name="objects"> <map> <entry key="org.krams.tutorial.domain.AdminPost"> <list> <value>READ</value> <value>WRITE</value> </list> </entry> <entry key="org.krams.tutorial.domain.PersonalPost"> <list> <value>READ</value> </list> </entry> <entry key="org.krams.tutorial.domain.PublicPost"> <list> <value>READ</value> <value>WRITE</value> </list> </entry> </map> </property> </bean> <!-- Declare permissions for User Contains a map of objects and their associated allowed actions --> <bean id="user" class="org.krams.tutorial.security.Permission" > <property name="objects"> <map> <entry key="org.krams.tutorial.domain.PersonalPost"> <list> <value>READ</value> <value>WRITE</value> </list> </entry> <entry key="org.krams.tutorial.domain.PublicPost"> <list> <value>READ</value> <value>WRITE</value> </list> </entry> </map> </property> </bean> <!-- Declare permissions for Visitor Contains a map of objects and their associated allowed actions --> <bean id="visitor" class="org.krams.tutorial.security.Permission" > <property name="objects"> <map> <entry key="org.krams.tutorial.domain.PublicPost"> <list> <value>READ</value> </list> </entry> </map> </property> </bean> </beans>This configuration matches the requirements we laid in the tables earlier. Here are the tables again:
An admin has READ and WRITE access to everything, but only READ access to the Personal Posts. See the table below:
Admin
Post Type | View | Add | Edit | Delete |
Admin | x | x | x | x |
Personal | x | |||
Public | x | x | x | x |
A regular user has READ and WRITE access to Personal Posts and Public Posts but only READ access to Admin Posts. See the table below:
User
Post Type | View | Add | Edit | Delete |
Admin | ||||
Personal | x | x | x | x |
Public | x | x | x | x |
A visitor can only read Admin and Public Posts but no access of whatsoever in the Personal Posts section. See the table below:
Visitor
Post Type | View | Add | Edit | Delete |
Admin | ||||
Personal | ||||
Public | x |
The Custom Permission
To store our objects and allowed actions, we used a custom org.krams.tutorial.security.Permission class.Permission.java
package org.krams.tutorial.security; import java.util.List; import java.util.Map; /** * Contains a map of objects and their associated allowed actions */ public class Permission { /** * A Map containing a list of objects and their corresponding actions * <p> * String: key name of the object * List<String>: a list of permissions */ private Map<String, List<String>> objects; public Map<String, List<String>> getObjects() { return objects; } public void setObjects(Map<String, List<String>> objects) { this.objects = objects; } }This is a simple class containing a Map and a List.
The Custom PermissionEvaluator
Next, we implement the PermissionEvaluator interface by creating a custom class:CustomPermissionEvaluator.java
package org.krams.tutorial.security; import java.io.Serializable; import java.util.Collection; import java.util.Map; import org.apache.log4j.Logger; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import javax.annotation.Resource; /** * A custom PermissionEvaluator implementation that uses a Map to * check whether a domain Object and access level exists for a particular user. * This also uses RoleHiearchy to retrieve the highest role possible for the user. */ public class CustomPermissionEvaluator implements PermissionEvaluator { protected static Logger logger = Logger.getLogger("security"); @Resource(name="permissionsMap") private Map permissionsMap; @Resource(name="roleHierarchy") private RoleHierarchy roleHierarchy; /** * Evaluates whether the user has permission by delegating to * hasPermission(String role, Object permission, Object domain) */ public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { logger.debug("Evaluating expression using hasPermission signature #1"); logger.debug("Retrieving user's highest role"); String role = getRole(authentication); logger.debug("****************"); logger.debug("role: " + role); logger.debug("targetDomainObject: " + targetDomainObject); logger.debug("permission: " + permission); logger.debug("****************"); // Check the type of object logger.debug("User is trying to access the object: " + targetDomainObject); logger.debug("Check if user has permission"); // Delegate to another hasPermission signature return hasPermission(role, permission, targetDomainObject); } /** * Another hasPermission signature. We will not implement this. */ public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { logger.debug("Evaluating expression using hasPermission signature #2"); return false; } /** * Retrieves the user's highest role */ private String getRole(Authentication authentication) { String highestRole = null; try { Collectionauths = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()); for (GrantedAuthority auth: auths) { highestRole = auth.getAuthority(); break; } logger.debug("Highest role hiearchy: " + roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities())); } catch (Exception e) { logger.debug("No authorities assigned"); } return highestRole; } /** * Evaluates whether the user has permission */ private Boolean hasPermission(String role, Object permission, Object domain) { logger.debug("Check if role exists: " + role); if ( permissionsMap.containsKey(role) ) { logger.debug("Role exists: " + role); // Retrieve userPermission object Permission userPermission = (Permission) permissionsMap.get(role); // Check if domain exists in Map logger.debug("Check if domain exists: " + domain.getClass().getName()); if ( userPermission.getObjects().containsKey(domain.getClass().getName())){ logger.debug("Domain exists: " + domain.getClass().getName()); // Loop the internal list and see if the class' full name matches logger.debug("Check if permission exists: " + permission); for (String action: userPermission.getObjects().get(domain.getClass().getName()) ) { if (action.equals(permission)) { logger.debug("Permission exists: " + action); logger.debug("Permission Granted!"); return true; } } } } // By default, do not give permission logger.debug("Permission Denied!"); return false; } }
Configure Spring Security
Let's use our new classes and enable Spring Security at the same time.Here's the configuration:
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <!-- To enable Method Security Expressions and custom PermissionEvaluator we need to add the following --> <security:global-method-security pre-post-annotations="enabled"> <security:expression-handler ref="expressionHandler" /> </security:global-method-security> <!-- To use hasPermission() expressions, we have to configure a PermissionEvaluator --> <!-- See 15.3.2 Built-In Expression @http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html#el-permission-evaluator --> <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <property name="permissionEvaluator" ref="customPermissionEvaluator" /> <property name = "roleHierarchy" ref="roleHierarchy"/> </bean> <!-- Declare a custom PermissionEvaluator interface --> <bean class="org.krams.tutorial.security.CustomPermissionEvaluator" id="customPermissionEvaluator"/> <!-- This is where we configure Spring-Security --> <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" > <security:intercept-url pattern="/krams/auth/login" access="permitAll"/> <security:form-login login-page="/krams/auth/login" authentication-failure-url="/krams/auth/login?error=true" default-target-url="/krams/all/view"/> <security:logout invalidate-session="true" logout-success-url="/krams/auth/login" logout-url="/krams/auth/logout"/> </security:http> <!-- Declare an authentication-manager to use a custom userDetailsService --> <security:authentication-manager> <security:authentication-provider user-service-ref="userDetailsService"> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager> <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database --> <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/> <!-- An in-memory list of users. No need to access an external database layer. See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication --> <!-- john's password: admin jane's password: user mike's password: visitor --> <security:user-service id="userDetailsService"> <security:user name="john" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_ADMIN" /> <security:user name="jane" password="ee11cbb19052e40b07aac0ca060c23ee" authorities="ROLE_USER" /> <security:user name="mike" password="127870930d65c57ee65fcc47f2170d38" authorities="ROLE_VISITOR" /> </security:user-service> <!-- http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.html --> <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> <property name="hierarchy"> <value> ROLE_ADMIN > ROLE_USER ROLE_USER > ROLE_VISITOR </value> </property> </bean> </beans>
The key configuration here are the following (the rest are standard Spring Security 3 setup):
<!-- To enable Method Security Expressions and custom PermissionEvaluator we need to add the following --> <security:global-method-security pre-post-annotations="enabled"> <security:expression-handler ref="expressionHandler" /> </security:global-method-security> <!-- To use hasPermission() expressions, we have to configure a PermissionEvaluator --> <!-- See 15.3.2 Built-In Expression @http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html#el-permission-evaluator --> <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <property name="permissionEvaluator" ref="customPermissionEvaluator" /> <property name = "roleHierarchy" ref="roleHierarchy"/> </bean> <!-- Declare a custom PermissionEvaluator interface --> <bean class="org.krams.tutorial.security.CustomPermissionEvaluator" id="customPermissionEvaluator"/> <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> <property name="hierarchy"> <value> ROLE_ADMIN > ROLE_USER ROLE_USER > ROLE_VISITOR </value> </property> </bean>
Conclusion
We're done with setting-up the Spring Security section. Our next task is to integrate this application with Spring MVC. Visit the following link for the continuation: Spring Security: Simple ACL using Expression-Based Access Control (Part 2)
Share the joy:
|
Subscribe by reader Subscribe by email Share
Very Good tutorial! Its helped me lot.
ReplyDeleteThanks for sharing post.
Hi, can I have multiple permission providers? One each for each modules? This is because the business logic implemented is different and complex. Is it possible to configure multiple permission providers in security config file?
ReplyDeleteThanks/Joe
hi, it was a very good tutorial thanks a lot , i'm just wandering can this be deployed with a jsf framework ? if yes how can i proceed to do it ?
ReplyDeletehow to do automatic creation ,updation and deletion of acl database entries , as domain objects are added ,updated or deleted
ReplyDeleteI know, from Spring security we can control user access to certain methods.
ReplyDeleteBut my question is, can we control button level access based on user role through Spring Security.
Thanks
I have read your blog its very attractive and impressive. I like it your blog.
ReplyDeleteSpring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training
spring training in chennai spring hibernate training in chennai
Hello! This is my first visit to your blog! We are a team of volunteers and starting a new initiative in a community in the same niche. Your blog provided us useful information to work on. You have done an outstanding job.
ReplyDeleteAWS Online Training | Online AWS Certification Course - Gangboard
I’ve desired to post about something similar to this on one of my blogs and this has given me an idea. Cool Mat.
ReplyDeletepython training in tambaram | python training in annanagar | python training in jayanagar
Thanks you for sharing this unique useful information content with us. Really awesome work. keep on blogging
ReplyDeleteOnline DevOps Certification Course - Gangboard
Best Devops Training institute in Chennai
Wow it is really wonderful and awesome thus it is very much useful for me to understand many concepts and helped me a lot. it is really explainable very well and i got more information from your blog.
ReplyDeleterpa training in velachery| rpa training in tambaram |rpa training in sholinganallur | rpa training in annanagar| rpa training in kalyannagar
It is amazing and wonderful to visit your site.Thanks for sharing this information,this is useful to me...
ReplyDeleteJava training in Tambaram | Java training in Velachery
Java training in Omr | Oracle training in Chennai
Very nice post here and thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.
ReplyDeleteData Science training in kalyan nagar | Data Science training in OMR
Data Science training in chennai | Data science training in velachery
Data science training in tambaram | Data science training in jaya nagar
very nice....!
ReplyDeletebrunei darussalam hosting
inplant training in chennai
nice blogsss.............!
ReplyDeletedominican republic web hosting
iran hosting
palestinian territory web hosting
panama web hosting
syria hosting
services hosting
afghanistan shared web hosting
andorra web hosting
belarus web hosting
I have read your excellent post. Thanks for sharing
ReplyDeleteaws Training in Bangalore
python Training in Bangalore
hadoop Training in Bangalore
angular js Training in Bangalore
bigdata analytics Training in Bangalore
python Training in Bangalore
aws Training in Bangalore
kani..
ReplyDeletehosting
india hosting
india web hosting
iran web hosting
technology 11 great image sites like imgur hosting
final year project dotnet server hacking what is web hosting
macao web hosting
inplant training in chennai
ReplyDeleteinplant training in chennai
inplant training in chennai for it.php
slovakia web hosting
ReplyDeletetimor lestes hosting
egypt hosting
egypt web hosting
ghana hosting
iceland hosting
italy shared web hosting
jamaica web hosting
kenya hosting
kuwait web hosting
very good...
ReplyDeleteinternship report on python
free internship in chennai for ece students
free internship for bca
internship for computer science engineering students in india
internships in hyderabad for cse students 2018
electrical companies in hyderabad for internship
internships in chennai for cse students 2019
internships for ece students
inplant training in tcs chennai
internship at chennai
Thank you for sharing information. Wonderful blog & good post.
ReplyDeleteaws Training in Bangalore
python Training in Bangalore
hadoop Training in Bangalore
angular js Training in Bangalore
bigdata analytics Training in Bangalore
python Training in Bangalore
aws Training in Bangalore
very nice...
ReplyDeletecoronavirus update
inplant training in chennai
inplant training
inplant training in chennai for cse
inplant training in chennai for ece
inplant training in chennai for eee
inplant training in chennai for mechanical
internship in chennai
online internship
For online microsoft Certification Exams
ReplyDelete<1--- 1st azure set --->
online microsoft azure administrator certification exams edchart
online microsoft azure infrastructure & deployment certification exams edchart
online microsoft azure data analyst certification exams edchart
online microsoft-designing-an-azure-data-solution-certification exams edchart
online microsoft-azure-relational-database-admin-certification exams edchart
online microsoft-azure-iot-developer- certification exams edchart
online designing-and-implementing-microsoft-azure-devops-certification exams edchart
online microsoft-azure-security-technologies-certification exams edchart