Exercise 17.1: Security




Exercise 17.1: Security

This exercise secures the Titan Cruises Reservation system introduced in Exercise 11.4 in Workbook 9. It modifies the ProcessPayment EJB so that only authorized merchant users can invoke payment operations.

Configure JBoss Security

If JBoss is running, shut it down. You will need to make some configuration modifications to enable security for this exercise.

To enable security in the JBoss application server, you need to create a security domain . A security domain is a repository for users, passwords, and the roles with which each user is associated. The EJB container delegates to the security domain when performing authentication and authorization. Each container can be associated with a different domain.

Out of the box, JBoss supports three types of domains: relational databases, LDAP, and a flat file. For this example, we will use a clear-text flat file to store our users, passwords, and role associations. Security domains are configured in the jboss-4.0.x/server/default/conf/login-config.xml file. Open this file in your favorite editor and add the following XML within the <policy> element:

<application-policy name="TitanIdentityDB">
  <authentication>
      <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
                    flag = "required">
         <module-option name="usersProperties">
                   users-titan.properties
         </module-option>
         <module-option name="rolesProperties"> 

                   roles-titan.properties
         </module-option>
      </login-module>
  </authentication>
</application-policy>

The name attribute of the <application-policy> element is the name of your security domain. You will reference this name when you configure your EJBs for security. Two files are used to store security information; they are defined by the usersProperties and rolesProperties module options. The values reference a file that should be available as a resource in your Java classpath. For this exercise, these files are located in the src/resources directory. They are stored in the EJB-JAR file created by the Ant build script for this exercise. Let's look at each of them.

users-titan.properties
wburke=password 

richard=password

The users-titan.properties file is a set of name value pairs, where the name represents a user in our Titan Cruises reservation system and the value is the user's clear-text password. We have two users defined: wburke and richard.

roles-titan.properties
wburke=AUTHORIZED_MERCHANT
richard=UNAUTHORIZED_MERCHANT

The roles-titan.properties file defines which roles are associated with each user. It is a set of name value pairs, where the name is the user and the value is a comma-delimited list of roles with which the user is associated.

Start Up JBoss

After you have modified login-config.xml, restart it as instructed in Workbook 1.

Initialize the Database

The database tables will be created when Exercise 17.1 is deployed to JBoss. If you have problems running this example, shut down JBoss and run the clean.db Ant task.

Build and Deploy the Example Programs

Perform the following steps:

  1. Open a command prompt or shell terminal and change to the ex17_1 directory created by the extraction process.

  2. Set the JAVA_HOME and JBOSS_HOME environment variables to point to where your JDK and JBoss 4.0 are installed. Examples:


    Windows:

    C:\workbook\ex17_1> set JAVA_HOME=C:\jdk1.5.0
    C:\workbook\ex17_1> set JBOSS_HOME=C:\jboss-4.0.x
    


    Unix:

    $ export JAVA_HOME=/usr/local/jdk1.5.0
    $ export JBOSS_HOME=/usr/local/jboss-4.0
    

  3. Add ant to your execution path. Ant is the build utility.


    Windows:

    C:\workbook\ex17_1> set PATH=..\ant\bin;%PATH%
    


    Unix:

    $ export PATH=../ant/bin:$PATH
    

  4. Perform the build by typing ant.

As in the exercises in earlier chapters of this workbook, titan.jar is rebuilt, copied to the JBoss deploy directory, and redeployed by the application server.

Examine the ProcessPaymentBean Class

To enable security for the ProcessPayment EJB, we must apply the @org.jboss.annotation.security.SecurityDomain annotation to the com.titan.processpayment.ProcessPaymentBean class:

package com.titan.processpayment;

import com.titan.domain.*;

import java.sql.*;

import javax.ejb.*;
import javax.annotation.Resource;
import javax.sql.DataSource;
import javax.ejb.EJBException;
import org.jboss.annotation.security.SecurityDomain;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;

@Stateless

@SecurityDomain 
("TitanIdentityDB")
@RolesAllowed("AUTHORIZED_MERCHANT")
public class ProcessPaymentBean implements ProcessPaymentRemote,
                                           ProcessPaymentLocal
{

The value you specify for the @SecurityDomain annotation must match the security domain you configured earlier in login-config.xml. The bean class is also annotated with the default roles allowed for each method of this EJB:

   final public static String CASH = "CASH";
   final public static String CREDIT = "CREDIT";
   final public static String CHECK = "CHECK";

   @Resource(mappedName="java:/DefaultDS") DataSource dataSource;

   @Resource(name="min") int minCheckNumber = 100;
  
@PermitAll
   public boolean byCash(Customer customer, double amount)
      throws PaymentException
   {
      return process(customer.getId( ), amount, CASH, null, -1, null, null);
   }

   @RolesAllowed({"AUTHORIZED_MERCHANT", "CHECK_FRAUD_ENABLED"})
   public boolean byCheck(Customer customer, CheckDO check, double amount)
      throws PaymentException
   {
      if (check.checkNumber > minCheckNumber)
      {
         return process(customer.getId( ), amount, CHECK,
                        check.checkBarCode, check.checkNumber, null, null);
      }
      else
      {
         throw new PaymentException("Check number is too low. Must be
at least "+minCheckNumber);
      }
   }
   public boolean byCredit(Customer customer, CreditCardDO card,
                           double amount) throws PaymentException
   {

The rest of the bean methods are annotated the same way as the example in Chapter 17.

Examine the Client

The travelAgentShell class has been modified a little bit to support a login. The shell( ) method calls the login( ) method before it starts to receive commands:

   public void login( ) throws Exception
   {
      String prompt = "user: ";
      boolean hasUser = false;
      boolean hasPassword = false;
      System.out.println( );
      while (!hasPassword)
      {
         System.out.print(prompt);

         String command = "";
         char read = '\0';

        while (read != '\r' && read != '\n')
         {
            read = (char)System.in.read( );
            command = command + read;
         }
         // clear out newlines from system input
         int available = System.in.available( );
         for (int i = 0; i < available; i++) System.in.read( );

         command = command.trim( );
         if (command.equals(""))
         {
            continue;
         }
         if (!hasUser)
         {
            user = command;
            hasUser = true;
            prompt = "password: ";
         }
         else
         {
            password = command;
            hasPassword = true;
         }
      }
   }

The login( ) method retrieves the username and password from the command line and stores them in two member variables.

JBoss associates the username and password through the JNDI API. The getInitialContext( ) method performs this association:

   public Context getInitialContext( ) throws Exception
   {
      Properties env = new Properties( );
      env.setProperty(Context.SECURITY_PRINCIPAL, user);
      env.setProperty(Context.SECURITY_CREDENTIALS, password);
      env.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.jboss.security.jndi.JndiLoginInitialContextFactory");
      return new InitialContext(env); 

   }

Authentication does not take place when InitialContext is created; the EJB container is responsible for starting that process. We have not associated a security domain with the TravelAgent EJB. Authentication and authorization do not take place until the travelAgentBean.bookPassage( ) method invokes on the ProcessPayment EJB. In a real system, you probably would want to secure the TravelAgent EJB as well. The exercise was implemented in this way to show you that the security principal and credentials are propagated to nested EJB calls.

Run the Application

Run the TravelAgentShell application by invoking Shell.bat or the Shell.sh script at the command prompt. Remember to set your JBOSS_HOME and PATH environment variables. When you start up this program, it asks you for a username and a password. Specify one of the users defined in the users-titan.properties file. Only wburke will work:

C:\jboss\oreilly-ejb3\workbook\ex17_1>shell

********************
    Titan Cruises
********************


user: wburke
password: password

>

Interact with the Titan Cruises Reservation system the same way you did in Exercise 11.4 in Workbook 9. If you have entered a wrong username or password, the book command will fail with a javax.ejb.EJBAccessException thrown.