Spring MVC Section
We now move to the MVC section of the application.Domain Objects
First, let's declare our domain objects:AdminPost.java PersonalPost.java PublicPost.javaHere are the class declarations:
AdminPost.java
package org.krams.tutorial.domain; import java.util.Date; public class AdminPost { private String id; private Date date; private String message; public String getId() { return id; } public void setId(String id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
PersonalPost.java
package org.krams.tutorial.domain; import java.util.Date; public class PersonalPost { private String id; private Date date; private String message; public String getId() { return id; } public void setId(String id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
PublicPost.java
package org.krams.tutorial.domain; import java.util.Date; public class PublicPost { private String id; private Date date; private String message; public String getId() { return id; } public void setId(String id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
The Controllers
Next, we declare the controllers:AdminController.java PersonalController.java PublicController.java
Here are the class declarations:
AdminController.java
package org.krams.tutorial.controller; import org.apache.log4j.Logger; import org.krams.tutorial.domain.AdminPost; import org.krams.tutorial.service.AdminService; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.annotation.Resource; /** * Handles Admin-related requests */ @Controller @RequestMapping("/admin") public class AdminController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="adminService") private AdminService adminService; /** * Retrieves the Edit page */ @RequestMapping(value = "/edit", method = RequestMethod.GET) public String getEditPage(Model model) { logger.debug("Received request to view edit page"); // Call service. If true, we have appropriate authority if (adminService.edit(new AdminPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been edited successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Admin >> Edit"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } /** * Retrieves the Add page */ @RequestMapping(value = "/add", method = RequestMethod.GET) public String getAddPage(Model model) { logger.debug("Received request to view add page"); // Call service. If true, we have appropriate authority if (adminService.add(new AdminPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been added successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Admin >> Add"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } /** * Retrieves the Delete page */ @RequestMapping(value = "/delete", method = RequestMethod.GET) public String getDeletePage(Model model) { logger.debug("Received request to view delete page"); // Call service. If true, we have appropriate authority if (adminService.delete(new AdminPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been deleted successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Admin >> Delete"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } }
PersonalController.java
package org.krams.tutorial.controller; import org.apache.log4j.Logger; import org.krams.tutorial.domain.PersonalPost; import org.krams.tutorial.service.PersonalService; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.annotation.Resource; /** * Handles Personal-related requests */ @Controller @RequestMapping("/personal") public class PersonalController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="personalService") private PersonalService personalService; /** * Retrieves the Edit page */ @RequestMapping(value = "/edit", method = RequestMethod.GET) public String getEditPage(Model model) { logger.debug("Received request to view edit page"); // Call service. If true, we have appropriate authority if (personalService.edit(new PersonalPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been edited successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Personal >> Edit"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } /** * Retrieves the Add page */ @RequestMapping(value = "/add", method = RequestMethod.GET) public String getAddPage(Model model) { logger.debug("Received request to view add page"); // Call service. If true, we have appropriate authority if (personalService.add(new PersonalPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been added successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Personal >> Add"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } /** * Retrieves the Delete page */ @RequestMapping(value = "/delete", method = RequestMethod.GET) public String getDeletePage(Model model) { logger.debug("Received request to view delete page"); // Call service. If true, we have appropriate authority if (personalService.delete(new PersonalPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been deleted successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Personal >> Delete"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } }
PublicController.java
package org.krams.tutorial.controller; import org.apache.log4j.Logger; import org.krams.tutorial.domain.PublicPost; import org.krams.tutorial.service.PublicService; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.annotation.Resource; /** * Handles Public-related requests */ @Controller @RequestMapping("/public") public class PublicController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="publicService") private PublicService publicService; /** * Retrieves the Edit page */ @RequestMapping(value = "/edit", method = RequestMethod.GET) public String getEditPage(Model model) { logger.debug("Received request to view edit page"); // Call service. If true, we have appropriate authority if (publicService.edit(new PublicPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been edited successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Public >> Edit"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } /** * Retrieves the Add page */ @RequestMapping(value = "/add", method = RequestMethod.GET) public String getAddPage(Model model) { logger.debug("Received request to view add page"); // Call service. If true, we have appropriate authority if (publicService.add(new PublicPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been added successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Public >> Add"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } /** * Retrieves the Delete page */ @RequestMapping(value = "/delete", method = RequestMethod.GET) public String getDeletePage(Model model) { logger.debug("Received request to view delete page"); // Call service. If true, we have appropriate authority if (publicService.delete(new PublicPost()) == true) { // Add result to model model.addAttribute("result", "Entry has been deleted successfully!"); } else { // Add result to model model.addAttribute("result", "You're not allowed to perform that action!"); } // Add source to model to help us determine the source of the JSP page model.addAttribute("source", "Public >> Delete"); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/resultpage.jsp return "resultpage"; } }
We also declare a fourth controller that displays all records:
AllController.java
/** * */ package org.krams.tutorial.controller; import javax.annotation.Resource; import org.apache.log4j.Logger; import org.krams.tutorial.service.AdminService; import org.krams.tutorial.service.PersonalService; import org.krams.tutorial.service.PublicService; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Handles authentication related requests */ @Controller @RequestMapping("/all") public class AllController { protected static Logger logger = Logger.getLogger("controller"); @Resource(name="adminService") private AdminService adminService; @Resource(name="personalService") private PersonalService personalService; @Resource(name="publicService") private PublicService publicService; /** * Retrieves the View page. * <p> * This loads all authorized posts. */ @RequestMapping(value = "/view", method = RequestMethod.GET) public String getViewAllPage(Model model) { logger.debug("Received request to view all page"); // Retrieve items from service and add to model model.addAttribute("adminposts", adminService.getAll()); model.addAttribute("personalposts", personalService.getAll()); model.addAttribute("publicposts", publicService.getAll()); // Add our current role and username model.addAttribute("role", SecurityContextHolder.getContext().getAuthentication().getAuthorities()); model.addAttribute("username", SecurityContextHolder.getContext().getAuthentication().getName()); // This will resolve to /WEB-INF/jsp/bulletinpage.jsp return "bulletinpage"; } }
The Services
Next, we declare the corresponding services:AdminService.java PersonService.java PublicService.javaHere are the class declarations:
AdminService.java
package org.krams.tutorial.service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.krams.tutorial.domain.AdminPost; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @Service("adminService") public class AdminService { private Map<String, AdminPost> adminPosts = new HashMap<String, AdminPost>(); public AdminService() { // Initiliaze our in-memory HashMap list init(); } // filterObject refers to the current object in the collection @PostFilter("hasPermission(filterObject, 'READ')") public List<adminpost> getAll() { // Iterate our HashMap list and convert it to an ArrayList List<adminpost> adminList = new ArrayList<adminpost>(); for (String key: adminPosts.keySet()) { adminList.add(adminPosts.get(key)); } // Return our new list return adminList; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean add(AdminPost post) { // This will return true if it's accessible return true; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean edit(AdminPost post) { // This will return true if it's accessible return true; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean delete(AdminPost post) { // This will return true if it's accessible return true; } // Initiliazes an in-memory HashMap list private void init() { // Create new post AdminPost post1 = new AdminPost(); post1.setId(UUID.randomUUID().toString()); post1.setDate(new Date()); post1.setMessage("This is admin's post #1"); // Create new post AdminPost post2 = new AdminPost(); post2.setId(UUID.randomUUID().toString()); post2.setDate(new Date()); post2.setMessage("This is admin's post #2"); // Create new post AdminPost post3 = new AdminPost(); post3.setId(UUID.randomUUID().toString()); post3.setDate(new Date()); post3.setMessage("This is admin's post #3"); // Add to adminPosts adminPosts.put(post1.getId(), post1); adminPosts.put(post2.getId(), post2); adminPosts.put(post3.getId(), post3); } }
PersonalService.java
package org.krams.tutorial.service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.krams.tutorial.domain.PersonalPost; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @Service("personalService") public class PersonalService { private Map<String, PersonalPost> personalPosts = new HashMap<String, PersonalPost>(); public PersonalService() { // Initiliaze our in-memory HashMap list init(); } // filterObject refers to the current object in the collection @PostFilter("hasPermission(filterObject, 'READ')") public List<personalpost> getAll() { // Iterate our HashMap list and convert it to an ArrayList List<personalpost> personalList = new ArrayList<personalpost>(); for (String key: personalPosts.keySet()) { personalList.add(personalPosts.get(key)); } // Return our new list return personalList; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean add(PersonalPost post) { // This will return true if it's accessible return true; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean edit(PersonalPost post) { // This will return true if it's accessible return true; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean delete(PersonalPost post) { // This will return true if it's accessible return true; } // Initiliazes an in-memory HashMap list private void init() { // Create new post PersonalPost post1 = new PersonalPost(); post1.setId(UUID.randomUUID().toString()); post1.setDate(new Date()); post1.setMessage("This is personal's post #1"); // Create new post PersonalPost post2 = new PersonalPost(); post2.setId(UUID.randomUUID().toString()); post2.setDate(new Date()); post2.setMessage("This is personal's post #2"); // Create new post PersonalPost post3 = new PersonalPost(); post3.setId(UUID.randomUUID().toString()); post3.setDate(new Date()); post3.setMessage("This is personal's post #3"); // Add to personalPosts personalPosts.put(post1.getId(), post1); personalPosts.put(post2.getId(), post2); personalPosts.put(post3.getId(), post3); } }
PublicService.java
package org.krams.tutorial.service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.krams.tutorial.domain.PublicPost; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @Service("publicService") public class PublicService { private Map<String, PublicPost> publicPosts = new HashMap<String, PublicPost>(); public PublicService() { // Initiliaze our in-memory HashMap list init(); } // filterObject refers to the current object in the collection @PostFilter("hasPermission(filterObject, 'READ')") public List<publicpost> getAll() { // Iterate our HashMap list and convert it to an ArrayList List<publicpost> publicList = new ArrayList<publicpost>(); for (String key: publicPosts.keySet()) { publicList.add(publicPosts.get(key)); } // Return our new list return publicList; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean add(PublicPost post) { // This will return true if it's accessible return true; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean edit(PublicPost post) { // This will return true if it's accessible return true; } @PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean delete(PublicPost post) { // This will return true if it's accessible return true; } // Initiliazes an in-memory HashMap list private void init() { // Create new post PublicPost post1 = new PublicPost(); post1.setId(UUID.randomUUID().toString()); post1.setDate(new Date()); post1.setMessage("This is public's post #1"); // Create new post PublicPost post2 = new PublicPost(); post2.setId(UUID.randomUUID().toString()); post2.setDate(new Date()); post2.setMessage("This is public's post #2"); // Create new post PublicPost post3 = new PublicPost(); post3.setId(UUID.randomUUID().toString()); post3.setDate(new Date()); post3.setMessage("This is public's post #3"); // Add to publicPosts publicPosts.put(post1.getId(), post1); publicPosts.put(post2.getId(), post2); publicPosts.put(post3.getId(), post3); } }
Observations
Notice the service methods have been annotated with @PreAuthorize and @PostFilter. Also, we're not actually performing any add, edit, or delete actions. Instead we're just returning a Boolean value to test whether the method is accessible.The following declaration means check the domain object post and see if the current user has WRITE access to this object:
@PreAuthorize("hasPermission(#post, 'WRITE')") public Boolean add(PublicPost post) { // This will return true if it's accessible return true; }
Whereas the following declaration means after returning, check the domain object post and see if the current user has READ access to this object:
@PostFilter("hasPermission(filterObject, 'READ')") public List<publicpost> getAll() { // Iterate our HashMap list and convert it to an ArrayList List<publicpost> publicList = new ArrayList<publicpost>(); for (String key: publicPosts.keySet()) { publicList.add(publicPosts.get(key)); } // Return our new list return publicList; }
Run the Application
Let's run the application to see the results (To see the remaining XML configuration, please download the source code below):We'll need to login first. Enter the following URL to login:
http://localhost:8080/spring-security-acl-expression/krams/auth/login
To access the Bulletin Page, enter the following URL:
http://localhost:8080/spring-security-acl-expression/krams/all/view
We will log-in first as an admin using john/admin as the username/password pair.
Next, we'll login as a user using jane/user as the username/password pair.
Then, we'll login as a visitor using mike/visitor as the username/password pair.
Notice the admin can see all posts while the user can see only the Personal and Public posts; whereas the visitor can only see the Public posts. We've also indicated at the top the name of the current user and the associated role.
If any of the three tries to access an unauthorized resource for their role, they will get the following:
This is the benefit of ACL. We're restricting access on the domain level, not just on the URL level. However if we use the normal intercept-url setup, we won't be able to display all three types of posts. It's either we see everything or we get denied. Try clicking on the remaining links, and verify if they are really protected. I'll bet you they are :)
Conclusion
That's it. We've finished our simple ACL application that leverages Spring Security's Expression-Based Access Control. It may look complicated at first but the concepts are really simple. Also, instead of going the heavyweight solution, we opted to use the lightweight solution using the hasPermission() expression and a custom PermissionsEvaluator implementationDownload the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-acl-expression/
You can download the project as a Maven build. Look for the spring-security-acl-expression.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share
Hi,
ReplyDeleteThanks for htis Nice artilce just to add while discussing about HashMap its worth mentioning following questions which frequently asked in Java interviews now days like How HashMap works in Java or How get() method of HashMap works in JAVA very often. on concept point of view these questions are great and expose the candidate if doesn't know deep details.
Javin
FIX Protocol tutorial
Thanks for this great tutorial.I was just wondering about how can this approach apply with accesscontrollist taglib. Should I impplement own aclServcie?
ReplyDeleteIs there a way to change user role(s) after he log-in ? without force the user to re-login?
ReplyDeleteI have a scenario which you can become ADMIN(or any other role) for example , how to do it?
Thanks ! great site
Hi,
ReplyDeleteThanks for your good tutorials.I run the application and not logged into the application if i am using the given password spring-security.xml.
Like for username john and password 21232f297a57a5a743894a0e4a801fc3 . But the application will giving the message "You have entered an invalid username or password!". Please advise me how can we logged in the application.
21232f297a57a5a743894a0e4a801fc3 = admin
DeleteHello,
ReplyDeleteI have problem with getall when there are 1000 row de post. Spring call haspermission 1000 and the performance is very low. Do you know how to fix it?
Thanks
The tutorial is designed to demonstrate the feature. I did not consider the performance, and I wouldn't be surprised it slows. I would suggest investigating the full ACL feature of Spring Security. Try reading my blog about it.
DeleteGreat tutorial, Thank you Mark!
ReplyDeleteWhat is your idea of implementing Attribute-based access control with spring security? Spring security doesn't directly support ABAC.
Thank you it's what I was looking for!
ReplyDeleteHi,
ReplyDeletei run this application but i got a problem, when i am login with any role_user and i am doing any action same(add, delete, edit) from any object it give me message successfully done. i don't know why the Expressions not working.
Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.
ReplyDeletecore java training in Electronic City
Hibernate Training in electronic city
spring training in electronic city
java j2ee training in electronic city