In this article, we will see the various aspects in securing an EJB Application. Security is vital not only for an enterprise application but also for any kind of application. It is essential to identify the system or the users accessing the applications and to provide access or denial for resources within the application based on some criteria. The point is that not every user should be given the rights to access sensible data and there must be some identification mechanism to distinguish this boundary. EJB specification for security falls in Declarative and Programmatic mode, the EJB Container owns the responsibility of establishing security for enterprise beans in the former mode whereas the application developer has to embed security specific code in the enterprise bean in the latter mode.
also read:
Security
Implementing the EJB security is one of the challenging task in J2EE projects. While talking about securing EJB Applications, we essentially talk about Authentication and Authorization. Authentication involves the process of verifying the user who is accessing a system. The most common way of providing online authentication for users is to supply credentials in the form of username and password and then asking the user to provide them before accessing the system. Not every user should be give access to various parts of a a system and Authorization refers to the process of ensuring whether the authenticated user has the required privilege to access a particular area in system.
It is worthwhile to understand the relationship between users, user groups and roles before delving into the details of EJB security. For easier maintenance of security model, the responsibilities of various users within an organization will be identified and they will segregated into various user groups. For example, one set of users in an organization will own the responsibility of carrying out admin set of tasks and they can be identified and made to fall into admin user group. Similarly users performing management oriented activities may fall in the manager user group.
Let us assume that the developer wants to write an enterprise bean containing business methods representing admin and management set of functionality. He may not be aware of the users and the user groups in the target environment. So, let us say that we write business methods adminOperations() and managementOperations(), then will somehow provide an indication that the method adminOperations() can be accessible by users owning the ‘admin’ role and the method managementOperations() can be accessible by users owning the ‘manager’ role. The role provides an abstract representation here, and in the target environment each role has to be mapped against the user groups which already exists in the operational environment.
Declarative Security
In this section, we will look into the concept of Declarative Security. By specifying this form of security for an Enterprise bean, we instruct the container that we are delegating the responsibility of security to the Container. EJB allows security elements to be specified at the class level and also at the method level.
Annotations
The various annotations that can be used with Enterprise beans are defined in the forthcoming sections. Note that it is also possible to define the security constraints for an Enterprise bean in a deployment descriptor. If the security elements are defined both in annotations and in deployment descriptor, the one specified in deployment descriptor takes precedence.
DeclareRoles annotation
This annotation can be specified at the class level and this will accept the list of allowed roles that an enterprise bean expects. Note that this annotation is optional, meaning that it is not necessary to declare the list of annotations before using it.
@DeclareRoles({"admin" "super-admin"}) public class ManagementBean{ }
RolesAllowed annotation
This annotation can be applied at the method level which will take an array of allowed roles for the method to execute. For example, in the below snippet we have declared roles ‘admin’ and ‘super-admin’, and in the delete() method we have only allowed users who belong to the role ‘super-admin’ to perform the delete operation.
@DeclareRoles({"admin" "super-admin"}) public class ManagementBean{ @RolesAllowed({"super-admin"}) public void delete(){ } }
PermitAll annotation
This annotation can be applied at the class level or at the method level. This instructs the container that a user belonging to any role is allowed to access the operation. When applied at the class level, this annotation will propagate to all the business methods in the enterprise bean.
@DeclareRoles({"admin" "super-admin"}) public class ManagementBean{ @RolesAllowed({"super-admin"}) public void delete(){ } @PermitAll public void viewOperation(){ } }
For example, in the above code, we have annotated the method ‘viewOperation’ with the @PermitAll annotation. This ensures that this method can be accessible by any user.
DenyAll annotation
This annotation can be applied both at the class and the method level. When applied at the class level, all the business methods within the enterprise bean cannot be accessible at all.
@DeclareRoles({"admin" "super-admin"}) public class ManagementBean{ @RolesAllowed({"super-admin"}) public void delete(){ } @DenyAll public void deleteAll(){ } }
Demonstration
In this example, we will provide with an example illustrating the usage of declarative security in ejbs. We will take the example of task management. Tasks can be created, assigned, completed and then deleted. We will identify four distinct roles here: ‘admin’, ‘manager’, ‘team-lead’ and ‘developer’. For illustration purposes, let us assume that only a manager can create a task, a team-lead can assign a created task to a developer and the developer can complete a task once it is completed. Special privileges are given to admin for deleting tasks.
Task Entity Model
We will define the model for Task Entity in this section. The listing for the Task Entity model is given below.
package net.javabeat.articles.ejb3.security.annotations; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class TaskEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String description; private String assignee; private String status; public String getAssignee() { return assignee; } public void setAssignee(String assignee) { this.assignee = assignee; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public int hashCode() { int hash = 0; hash += (id != null ? id.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { if (!(object instanceof TaskEntity)) { return false; } TaskEntity other = (TaskEntity) object; if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { return false; } return true; } @Override public String toString() { return "net.javabeat.articles.ejb3.security.annotations.TaskEntity[id=" + id + "]"; } }
Note that the above class is modeled as an Entity bean so that the task’s information can be persisted in the database. The task has properties such as task id, name, description, assignee and status. Note that we have used JPA annotations to instruct that above class is an Entity class.
Task Entity Remote interface
We will introduce the remote interface for the Task Entity in this section, the code for the same is given below.
package net.javabeat.articles.ejb3.security.annotations; import java.util.List; import javax.ejb.Remote; @Remote public interface TaskEntityFacadeRemote { void createTask(String name, String description); void assignTask(Long taskId, String assignee); void completeTask(Long taskId); List getAllTasks(); void deleteAllTasks(); void create(TaskEntity taskEntity); void edit(TaskEntity taskEntity); void remove(TaskEntity taskEntity); TaskEntity find(Object id); List findAll(); }
Other than the regular CRUD operations that can be applicable to any entity, this interface provides services for creating task, assigning task, completing task, listing all the tasks and deleting tasks.
Task Entity Remote Implementation
The implementation for the above remote interface is given below. Note that the below implementation takes new support such as dependency injection and annotations. The implementation class declares the list of roles through the annotation @DelcareRoles at the class level. The entity manager entity will be injected by the EJB Container with the help of annotation @PersistenceContext.
package net.javabeat.articles.ejb3.security.annotations; import java.util.List; import javax.annotation.security.DeclareRoles; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless @DeclareRoles(value={"admin", "manager", "team-lead", "developer"}) public class TaskEntityFacade implements TaskEntityFacadeRemote { @PersistenceContext private EntityManager em; @RolesAllowed("manager") public void createTask(String name, String description) { TaskEntity newTaskEntity = new TaskEntity(); newTaskEntity.setName(name); newTaskEntity.setDescription(description); newTaskEntity.setStatus("NEW"); em.persist(newTaskEntity); } @RolesAllowed("team-lead") public void assignTask(Long taskId, String assignee){ TaskEntity taskEntity = find(taskId); taskEntity.setAssignee(assignee); taskEntity.setStatus("ASSIGNED"); em.persist(taskEntity); } @RolesAllowed("developer") public void completeTask(Long taskId){ TaskEntity taskEntity = find(taskId); taskEntity.setStatus("COMPLETED"); em.persist(taskEntity); } @RolesAllowed("admin") public void deleteAllTasks(){ List allTasks = findAll(); if (allTasks != null){ for (TaskEntity aTask : allTasks){ remove(aTask); } } } @PermitAll public List getAllTasks() { return em.createQuery("select object(o) from TaskEntity as o").getResultList(); } public TaskEntity findByName(String name){ return null; } public void create(TaskEntity taskEntity) { em.persist(taskEntity); } public void edit(TaskEntity taskEntity) { em.merge(taskEntity); } public void remove(TaskEntity taskEntity) { em.remove(em.merge(taskEntity)); } public TaskEntity find(Object id) { return em.find(TaskEntity.class, id); } public List findAll() { return em.createQuery("select object(o) from TaskEntity as o").getResultList(); } }
Since in our example case, only a manager is allowed to create a task, hence the method ‘createTask()’ is annotated with @RolesAllowed with the role ‘manager’. The method assignTask can be executed by an user who owns the role ‘team-lead’. Similarly the methods completeTask() and deleteAllTasks() are assigned with ‘developer’ and ‘admin’ roles. The annotation @PermitAll is used against the method ‘getAllTasks()’ indicating that a user belonging to any role can access this operation.
Persitence configuration
The persistence configuration information file for persisting the Task Entity bean is given below.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="TaskManager-ejbPU" transaction-type="JTA"> <provider>oracle.toplink.essentials.PersistenceProvider</provider> <jta-data-source>jdbc/sample</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="toplink.ddl-generation" value="drop-and-create-tables"/> </properties> </persistence-unit> </persistence>
Note that we are using the Oracle’s TopLink as the persistence provider although any JPA compliant provider can be used. The ddl generation policy is ‘drop and create’ which means that before every execution the database objects will be dropped and re-created.
Security configuration
We will discuss the security configuration in this section. Note that in the enterprise bean we have defined only roles and not users. The Enterprise beans that will be getting deployed in the target environment will have a list of users and user groups configured. So, the roles defined in the Enterprise beans have to be mapped against the user groups in the target environment.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd"> <sun-ejb-jar> <security-role-mapping> <role-name>admin</role-name> <group-name>admin-group</group-name> </security-role-mapping> <security-role-mapping> <role-name>team-lead</role-name> <group-name>team-lead-group</group-name> </security-role-mapping> <security-role-mapping> <role-name>developer</role-name> <group-name>developer-group</group-name> </security-role-mapping> <security-role-mapping> <role-name>manager</role-name> <group-name>manager-group</group-name> </security-role-mapping> <enterprise-beans/> </sun-ejb-jar>
In the above configuration file, we have mapped the roles ‘admin’, ‘team-lead’, ‘developer’, ‘manager’ to the groups ‘admin-group’, ‘team-lead-group’, ‘developer-group’ and ‘manager-group’ respectively. Note that creation of user and user groups will vary across application servers.
Application client
The application client is given below. It does the job of invoking various services defined in the enterprise beans like creating, assigning and completing tasks.
package taskmanager; import java.util.List; import javax.ejb.EJB; import net.javabeat.articles.ejb3.security.annotations.TaskEntity; import net.javabeat.articles.ejb3.security.annotations.TaskEntityFacadeRemote; public class Main { @EJB private static TaskEntityFacadeRemote taskEntityFacade; public static void main(String[] args) { // deleteAllTasks(); // createTasks(); Long taskIds[] = viewAllTasks(); // // assignTasks(taskIds, "abc"); // viewAllTasks(); // // completeTasks(taskIds); // viewAllTasks(); } private static void deleteAllTasks(){ taskEntityFacade.deleteAllTasks(); } private static void createTasks(){ taskEntityFacade.createTask("Create new feature", "To create a new feature for some project"); taskEntityFacade.createTask("To fix performance issue", "To fix performance issue"); } private static Long[] viewAllTasks(){ Long[] taskIds = new Long[2]; System.out.println("View All Tasks"); List allTasks = taskEntityFacade.getAllTasks(); for (int index = 0; index < allTasks.size(); index ++){ TaskEntity aTaskEntity = allTasks.get(index); String str = aTaskEntity.getId() + ","; str = aTaskEntity.getName() + ","; str = str + aTaskEntity.getDescription() + ","; str = str + aTaskEntity.getStatus() + ","; taskIds[index] = aTaskEntity.getId(); System.out.println(str); } return taskIds; } private static void assignTasks(Long[] taskIds, String assignee){ for (int index = 0; index < taskIds.length; index ++){ Long taskId = taskIds[index]; System.out.println("Task Id is " + taskId); taskEntityFacade.assignTask(taskId, assignee); } } private static void completeTasks(Long[] taskIds){ for (int index = 0; index < taskIds.length; index ++){ Long taskId = taskIds[index]; taskEntityFacade.completeTask(taskId); } } }
Note that when invoking an EJB method that is secured, a dialog box will be shown prompting for username and password. The username and the password will be compared with the users that are already configured in the application server.
Programmatic Security
In most of the situations, it is sufficient for applications to take the advantage of declarative security services provided by the container. However, in certain other situations, this may not be the case. For example, let us assume the scenario of a registered user downloading files from a portal. The download operation has to check whether the user belongs to the group of registered users for which case the declarative security case is sufficient. Now, let us assume that we want to prevent the user from downloading resource that has exceeded the maximum limit of ten. Now we run into a dynamic condition and based on that we allow or deny a particular operation to the user. This is where the programmatic security comes into picture.
Demonstration
We will take the example of user trying to perform various operations on a blog for illustrating programmatic security. Considering the operations – replying to a post in the blog and downloading files from the blog. A user is allowed to reply to a post only when he is a registered user and the number of posts created by the user is more than 10. Similarly for the download operation, other then being a registered user, the user has to be a premium user to perform the download operation.
User Entity
The user entity model is given below. Note that we have made use of JPA annotations to indicate that this model is a JPA entity.
package net.javabeat.articles.ejb3.security.programatic; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity(name="User") @Table(name = "T_USER") public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String userId; private boolean isPremiumUser; private int postsCount; public boolean getIsPremiumUser() { return isPremiumUser; } public void setIsPremiumUser(boolean isPremiumUser) { this.isPremiumUser = isPremiumUser; } public int getPostsCount() { return postsCount; } public void setPostsCount(int postsCount) { this.postsCount = postsCount; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public int hashCode() { int hash = 0; hash += (id != null ? id.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { if (!(object instanceof User)) { return false; } User other = (User) object; if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { return false; } return true; } @Override public String toString() { return "net.javabeat.articles.ejb3.security.programatic.User[id=" + id + "]"; } }
We have declared ‘userId’, ‘isPremiumUser’ and ‘postsCount’ as the properties for the User Entity.
User Remote Interface
The remote user interface for performing various operations such as replying to a posting and downloading resources from a blog is given below.
package net.javabeat.articles.ejb3.security.programatic; import java.util.List; import javax.ejb.Remote; @Remote public interface UserFacadeRemote { void deleteAllUsers(); void create(User user); void create(String userId, boolean premiumUser, int postsCount); void downloadAttachment(Long userId); void edit(User user); void remove(User user); User find(Object id); List findAll(); void replyPost(Long userId, String comment); }
Note that other than the service methods for creating users, replying to a post and downloading resources, it contains the regular functionalities for performing CRUD operations. Note that we are not going to provide real-time implementations for the service methods.
User Interface Implementation
The implementation class for the above remote interface is given below. Note that the declared role for this class is ‘registered_role’. Note that through dependency injection, we have injected the objects EntityManager and SessionContext. In the replyPost() method, other than checking whether the user invoking the method belongs to the desired role, additional check is performed for the number of created posts.
package net.javabeat.articles.ejb3.security.programatic; import java.security.Principal; import java.util.List; import javax.annotation.Resource; import javax.annotation.security.DeclareRoles; import javax.annotation.security.RolesAllowed; import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless @DeclareRoles({"registered_role"}) public class UserFacade implements UserFacadeRemote { @PersistenceContext private EntityManager em; @Resource private SessionContext context; public void deleteAllUsers(){ List allUsers = findAll(); if (allUsers != null){ for (User user : allUsers){ remove(user); } } } public void create(String userId, boolean premiumUser, int postsCount) { User newUser = new User(); newUser.setIsPremiumUser(premiumUser); newUser.setPostsCount(postsCount); newUser.setUserId(userId); em.persist(newUser); } public void create(User user) { em.persist(user); } public void edit(User user) { em.merge(user); } public void remove(User user) { em.remove(em.merge(user)); } public User find(Object id) { return em.find(User.class, id); } public List findAll() { return em.createQuery("select object(o) from User as o").getResultList(); } @RolesAllowed("registered_role") public void replyPost(Long userId, String comment){ Principal principal = context.getCallerPrincipal(); System.out.println("Name " + principal.getName()); if (context.isCallerInRole("registered_role")){ User userObject = find(userId); if (!(userObject.getPostsCount() > 10)){ throw new SecurityException("Post creation not allowed"); }else{ // All creating posts } } } @RolesAllowed("registered_role") public void downloadAttachment(Long userId){ Principal principal = context.getCallerPrincipal(); System.out.println("Name " + principal.getName()); if (context.isCallerInRole("registered_role")){ User userObject = find(userId); if (!(userObject.getIsPremiumUser())){ throw new SecurityException("Downloading allowed only for premium users"); }else{ // Allow to download the attachment } } } }
For the downloadAttachment() operation, we are checking if the invoking user is a registered user and if the user is a premium user. We are making use of the Context object here to find out if the invoking user is in the desired role through the ‘isCallerInRole()’ method. Other methods of interest in the Context object with respect to the Security is the getCallerPrincipal() method which provides an encapsulatation of the user credentails that is created in the EJB container.
Conclusion
This article started with explaining the various security aspects supported by EJB Security. It then explained the declarative security model with the list of annotations supported, which was then followed by a demonstration. Since declarative security model may not be flexible and suitable in all scenarios, programmatic mode of security is also explained with the help of an example.
also read: