15 min read

Authentication and authorization in Java EE Container Security

There are two aspects covered by Java EE container security: authentication and authorization. Authentication is the process of verifying that users are who they claim to be. Typically this is performed by the user providing credentials such as a password. Authorization, or access control, is the process of restricting operations to specific users or categories of users. The EJB specification provides two kinds of authorization: declarative and programmatic, as we shall see later in the article.

The Java EE security model introduces a few concepts common to both authentication and authorization. A principal is an entity that we wish to authenticate. The format of a principal is application-specific but an example is a username. A role is a logical grouping of principals. For example, we can have administrator, manager, and employee roles. The scope over which a common security policy applies is known as a security domain, or realm.

Authentication

For authentication, every Java EE compliant application server provides the Java Authentication and Authorization Service (JAAS) API. JAAS supports any underlying security system. So we have a common API regardless of whether authentication is username/password verification against a database, iris or fingerprint recognition for example. The JAAS API is fairly low level and most application servers provide authentication mechanisms at a higher level of abstraction. These authentication mechanisms are application-server specific however. We will not cover JAAS any further here, but look at authentication as provided by the GlassFish application server.

GlassFish Authentication

There are three actors we need to define on the GlassFish application server for authentication purposes: users, groups, and realms. A user is an entity that we wish to authenticate. A user is synonymous with a principal. A group is a logical grouping of users and is not the same as a role. A group’s scope is global to the application server. A role is a logical grouping of users whose scope is limited to a specific application. Of course for some applications we may decide that roles are identical to groups. For other applications we need some mechanism for mapping the roles onto groups. We shall see how this is done later. A realm, as we have seen, is the scope over which a common security policy applies.

GlassFish provides three kinds of realms: file, certificate, and admin-realm. The file realm stores user, group, and realm credentials in a file named keyfile. This file is stored within the application server file system. A file realm is used by web clients using http or EJB application clients. The certificate realm stores a digital certificate and is used for authenticating web clients using https. The admin-realm is similar to the file realm and is used for storing administrator credentials. GlassFish comes pre-configured with a default file realm named file.

We can add, edit, and delete users, groups, and realms using the GlassFish administrator console. We can also use the create-file-user option of the asadmin command line utility. To add a user named scott to a group named bankemployee, in the file realm, we would use the command:

   --passwordfile userpassword --groups bankemployee scott"/>
    
    • –user specifies the GlassFish administrator username, admin in our example.
    • –passwordfile specifies the name of the file containing password entries. In our example this file is userpassword. Users, other than GlassFish administrators, are identified by AS_ADMIN_USERPASSWORD. In our example the content of the userpassword file is:
      AS_ADMIN_USERPASSWORD=xyz

      This indicates that the user’s password is xyz.

    • –groups specifies the groups associated with this user (there may be more than one group). In our example there is just one group, named bankemployee. Multiple groups are colon delineated. For example if the user belongs to both the bankemployee and bankcustomer groups, we would specify:
--groups bankemployee:bankcustomer
  • The final entry is the operand which specifies the name of the user to be created. In our example this is scott.

There is a corresponding asadmin delete-file-user option to remove a user from the file realm.

Mapping Roles to Groups

The Java EE specification specifies that there must be a mechanism for mapping local application specific roles to global roles on the application server. Local roles are used by an EJB for authorization purposes. The actual mapping mechanism is application server specific. As we have seen in the case of GlassFish, the global application server roles are called groups. In GlassFish, local roles are referred to simply as roles. Suppose we want to map an employee role to the bankemployee group. We would need to create a GlassFish specific deployment descriptor, sun-ejb-jar.xml, with the following element:

employeebankemployee

We also need to access the configuration-security screen in the administrator console. We then disable the Default Principal To Role Mapping flag. If the flag is enabled then the default is to map a group onto a role with the same name. So the bankemployee group will be mapped to the bankemployee role.

EJB 3 Security
We can leave the default values for the other properties on the configuration-security screen. Many of these features are for advanced use where third party security products can be plugged in or security properties customized. Consequently we will give only a brief description of these properties here.
  • Security Manager: This refers to the JVM security manager which performs code-based security checks. If the security manager is disabled GlassFish will have better performance. However, even if the security manager is disabled, GlassFish still enforces standard Java EE authentication/authorization.
  • Audit Logging: If this is enabled, GlassFish will provide an audit trail of all authentication and authorization decisions through audit modules. Audit modules provide information on incoming requests, outgoing responses and whether authorization was granted or denied. Audit logging applies for web-tier and ejb-tier authentication and authorization. A default audit module is provided but custom audit modules can also be created.
  • Default Realm: This is the default realm used for authentication. Applications use this realm unless they specify a different realm in their deployment descriptor. The default value is file. Other possible values are admin-realm and certificate. We discussed GlassFish realms in the previous section.
  • Default Principal: This is the user name used by GlassFish at run time if no principal is provided. Normally this is not required so the property can be left blank.
  • Default Principal Password: This is the password of the default principal.
  • JACC: This is the class name of a JACC (Java Authorization Contract for Containers) provider. This enables the GlassFish administrator to set up third-party plug in modules conforming to the JACC standard to perform authorization.
  • Audit Modules: If we have created custom modules to perform audit logging, we would select from this list.
  • Mapped Principal Class: This is only applicable when Default Principal to Role Mapping is enabled. The mapped principal class is used to customize the java.security.Principal implementation class used in the default principal to role mapping. If no value is entered, the com.sun.enterprise.deployment.Group implementation of java.security.Principal is used.

Authenticating an EJB Application Client

Suppose we want to invoke an EJB, BankServiceBean, from an application client. We also want the application client container to authenticate the client. There are a number of steps we first need to take which are application server specific. We will assume that all roles will have the same name as the corresponding application server groups. In the case of GlassFish we need to use the administrator console and enable Default Principal To Role Mapping.

Next we need to define a group named bankemployee with one or more associated users.

An EJB application client needs to use IOR (Interoperable Object Reference) authentication. The IOR protocol was originally created for CORBA (Common Object Request Broker Architecture) but all Java EE compliant containers support IOR. An EJB deployed on one Java EE compliant vendor may be invoked by a client deployed on another Java EE compliant vendor. Security interoperability between these vendors is achieved using the IOR protocol. In our case the client and target EJB both happen to be deployed on the same vendor, but we still use IOR for propagating security details from the application client container to the EJB container.

IORs are configured in vendor specific XML files rather than the standard ejb-jar.xml file. In the case of GlassFish, this is done within the element within the sun-ejb-jar.xml deployment descriptor file. We also need to specify the invoked EJB, BankServiceBean, in the deployment descriptor. An example of the sun-ejb-jar.xml deployment descriptor is shown below:

     Application Server 9.0 EJB 3.0//EN"
      "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">

  
    
      BankServiceBean
        
          
             USERNAME_PASSWORD
             default
             true
          
        
    
  
  • The as in stands for the IOR authentication service. This specifies authentication mechanism details.
  • The element specifies the authentication method. This is set to USERNAME_PASSWORD which is the only value for an application client.
  • The element specifies the realm in which the client is authenticated.
  • The element specifies whether the above authentication method is required to be used for client authentication.

When creating the corresponding EJB JAR file, the sun-ejb-jar.xml file should be included in the META-INF directory, as follows:


    
        
             
                          
              
              
            
                         
        
    

As soon as we run the application client, GlassFish will prompt with a username and password form, as follows:

EJB 3 Security
If we reply with the username scott and password xyz the program will run. If we run the application with an invalid username or password we will get the following error message:
javax.ejb.EJBException: nested exception is: java.rmi.AccessException: 
                                        CORBA NO_PERMISSION 9998 .....

EJB Authorization

Authorization, or access control, is the process of restricting operations to specific roles. In contrast with authentication, EJB authorization is completely application server independent. The EJB specification provides two kinds of authorization: declarative and programmatic. With declarative authorization all security checks are performed by the container. An EJB’s security requirements are declared using annotations or deployment descriptors. With programmatic authorization security checks are hard-coded in the EJBs code using API calls. However, even with programmatic authorization the container is still responsible for authentication and for assigning roles to principals.

Declarative Authorization

As an example, consider the BankServiceBean stateless session bean with methods findCustomer(), addCustomer() and updateCustomer():

package ejb30.session;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import ejb30.entity.Customer;
import javax.persistence.PersistenceContext;
import javax.annotation.security.RolesAllowed;
import javax.annotation.security.PermitAll;
import java.util.*;
@Stateless
@RolesAllowed("bankemployee")
public class BankServiceBean implements BankService {
   @PersistenceContext(unitName="BankService")
   private EntityManager em;
   private Customer cust;
   @PermitAll
   public Customer findCustomer(int custId) {
        return ((Customer) em.find(Customer.class, custId));
   }
   public void addCustomer(int custId, String firstName,
                                        String lastName) {
          cust = new Customer();
          cust.setId(custId);
          cust.setFirstName(firstName);
          cust.setLastName(lastName);
          em.persist(cust);
   }
   public void updateCustomer(Customer cust) {
   Customer mergedCust = em.merge(cust);
   }
}

We have prefixed the bean class with the annotation:

@RolesAllowed("bankemployee")

This specifies the roles allowed to access any of the bean’s method. So only users belonging to the bankemployee role may access the addCustomer() and updateCustomer() methods. More than one role can be specified by means of a brace delineated list, as follows:

@RolesAllowed({"bankemployee", "bankcustomer"})

We can also prefix a method with @RolesAllowed, in which case the method annotation will override the class annotation. The @PermitAll annotation allows unrestricted access to a method, overriding any class level @RolesAllowed annotation.

As with EJB 3 in general, we can use deployment descriptors as alternatives to the @RolesAllowed and @PermitAll annotations.

Denying Authorization

Suppose we want to deny all users access to the BankServiceBean.updateCustomer() method. We can do this using the @DenyAll annotation:

@DenyAll
    public void updateCustomer(Customer cust) {
       Customer mergedCust = em.merge(cust);
    }

Of course if you have access to source code you could simply delete the method in question rather than using @DenyAll. However suppose you do not have access to the source code and have received the EJB from a third party. If you in turn do not want your clients accessing a given method then you would need to use the element in the ejb-jar.xml deployment descriptor:

BankServiceBean

BankServiceBean
updateCustomer

EJB Security Propagation

Suppose a client with an associated role invokes, for example, EJB A. If EJB A then invokes, for example, EJB B then by default the client’s role is propagated to EJB B. However, you can specify with the @RunAs annotation that all methods of an EJB execute under a specific role.

For example, suppose the addCustomer() method in the BankServiceBean EJB invokes the addAuditMessage() method of the AuditServiceBean EJB:

@Stateless
@RolesAllowed("bankemployee")
public class BankServiceBean implements BankService {
private @EJB AuditService audit;
....
     public void addCustomer(int custId, String firstName,
                                                         String lastName) {
             cust = new Customer();
             cust.setId(custId);
             cust.setFirstName(firstName);
             cust.setLastName(lastName);
             em.persist(cust);
             audit.addAuditMessage(1, "customer add attempt");
     }
     ...
}

Note that only a client with an associated role of bankemployee can invoke addCustomer(). If we prefix the AuditServiceBean class declaration with @RunAs(“bankauditor”) then the container will run any method in AuditServiceBean as the bankauditor role, regardless of the role which invokes the method. Note that the @RunAs annotation is applied only at the class level, @RunAs cannot be applied at the method level.

@Stateless
@RunAs("bankauditor")
public class AuditServiceBean implements AuditService {
          @PersistenceContext(unitName="BankService")
          private EntityManager em;
          @TransactionAttribute(
                   TransactionAttributeType.REQUIRES_NEW)
          public void addAuditMessage (int auditId,
                                   String message) {
             Audit audit = new Audit();
             audit.setId(auditId);
             audit.setMessage(message);
             em.persist(audit);
          }
}

Programmatic Authorization

With programmatic authorization the bean rather than the container controls authorization. The javax.ejb.SessionContext object provides two methods which support programmatic authorization: getCallerPrincipal() and isCallerInRole(). The getCallerPrincipal() method returns a java.security.Principal object. This object represents the caller, or principal, invoking the EJB. We can then use the Principal.getName() method to obtain the name of the principal. We have done this in the addAccount() method of the BankServiceBean as follows:

Principal cp = ctx.getCallerPrincipal();
System.out.println("getname:" + cp.getName());

The isCallerInRole() method checks whether the principal belongs to a given role. For example, the code fragment below checks if the principal belongs to the bankcustomer role. If the principal does not belong to the bankcustomer role, we only persist the account if the balance is less than 99.

if (ctx.isCallerInRole("bankcustomer")) {
    em.persist(ac);
} else if (balance 

When using the isCallerInRole() method, we need to declare all the security role names used in the EJB code using the class level @DeclareRoles annotation:

@DeclareRoles({"bankemployee", "bankcustomer"})

The code below shows the BankServiceBean EJB with all the programmatic authorization code described in this section:

package ejb30.session;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import ejb30.entity.Account;
import javax.persistence.PersistenceContext;
import javax.annotation.security.RolesAllowed;
import java.security.Principal;
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.annotation.security.DeclareRoles;
import java.util.*;
@Stateless
@DeclareRoles({"bankemployee", "bankcustomer"})
public class BankServiceBean implements BankService {
       @PersistenceContext(unitName="BankService")
          private EntityManager em;
          private Account ac;
          @Resource SessionContext ctx;
          @RolesAllowed({"bankemployee", "bankcustomer"})
          public void addAccount(int accountId, double balance,
              String accountType) {
              ac = new Account();
              ac.setId(accountId);
              ac.setBalance(balance);
              ac.setAccountType(accountType);
              Principal cp = ctx.getCallerPrincipal();
              System.out.println("getname:" + cp.getName());
              if (ctx.isCallerInRole("bankcustomer")) {
                 em.persist(ac);
              } else if (balance 

Where we have a choice declarative authorization is preferable to programmatic authorization. Declarative authorization avoids having to mix business code with security management code. We can change a bean’s security policy by simply changing an annotation or deployment descriptor instead of modifying the logic of a business method. However, some security rules, such as the example above of only persisting an account within a balance limit, can only be handled by programmatic authorization. Declarative security is based only on the principal and the method being invoked, whereas programmatic security can take state into consideration.

Because an EJB is typically invoked from the web-tier by a servlet, JSP page or JSF component, we will briefly mention Java EE web container security. The web-tier and EJB tier share the same security model. So the web-tier security model is based on the same concepts of principals, roles and realms.


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here