Customized EJB security in JBoss

Separate your security policy from your business logic

Security is a key factor in developing many enterprise applications. Indeed, if you handle sensitive data or offer restricted services, you need a clearly defined security policy for your application and a framework that enforces it; you cannot let individual programmers guarantee that their code is safe. Server platforms such as J2EE (Java 2 Platform, Enterprise Edition) normally include some infrastructure to provide declarative security for your application components, usually via a role-based access-control system. Such systems do effectively move application security issues away from the programmer.

However, in some situations a security policy may define application-specific business rules beyond the declarative security model's scope. For an EJB-based (Enterprise JavaBeans) application, usually your only alternative is to place security code directly in the EJB business methods -- undesirably mixing business and security logic. The open source, J2EE-compliant JBoss application server offers a convenient proxy-based architecture for factoring out such custom security code into separate, configurable objects. In this article, I outline pressing reasons why you should use custom security, why you should keep it out of your business code, and how you can employ JBoss security proxies to secure your applications.

Note: You can download the source code examples for this article from the Resources section below.

Authorization mechanisms in an EJB server

As any article or book on the subject will tell you, a key area of application security is user-activity authorization: how do you control access to the components and services that your application offers? Once the system authenticates a user (typically by logging in with a name and password), it must check his every subsequent action to ensure that he has the necessary permissions. Most systems control access using a role-based mechanism -- they define a number of roles for the application, assign each user a subset of these roles, and specify the permissions in terms of the roles.

With such a mechanism, you delegate to the infrastructure -- in our case, a J2EE server -- the responsibility for applying the controls. The J2EE specification defines how to set up an application's roles and how to apply access controls to its services. To apply this mechanism to EJBs, you specify which roles can call which methods on a particular interface. When an EJB method is invoked on a user's behalf, then, before the invocation takes place on the bean implementation, the container checks whether the user has one of the roles required to call the method. If he doesn't, it raises a security exception and the method isn't called.

This all takes place outside the application code. The application's XML configuration files specify the security permissions, while the application server's security infrastructure defines the usernames and their associated roles. Scott Stark's "Integrate Security Infrastructures with JBossSX" (JavaWorld, August 2001) explains how to set up a typical J2EE application's security with JBossSX, and introduces the JAAS (Java Authentication and Authorization Service) architecture that JBoss uses to implement its security services. (I'd also like to thank Scott for providing useful feedback on this current article.)

So why keep security features out of your code? First, a bean coder may not be aware of how an individual bean will be used, so it makes sense to wait until you've assembled the beans to set the security constraints. Mainly, however, it's impractical and unreliable to put security code in every method you implement. Moreover, it's a nightmare to maintain such code if you decide to change the permissions for particular roles, add new roles, and so on, as these would be referenced throughout the code. So while it is possible in an EJB to obtain role information for the user calling the method, you should generally favor letting the container do the work.

Limitations of security-unaware applications

Forcing the container to enforce security policies frees the developer to code the application's business logic without worrying about security. It also allows the security policy and business logic to evolve separately.

However, those two are not necessarily completely independent. What if you want to apply security policies that depend on the application's business logic? Cases exist where simple role-based access control proves inadequate: there are no standard means to define policies that relate to specific bean instances, business data, or users. Example situations include:

  • A customer can access only her own account
  • A trader can only execute transactions that have a value less than one million Swiss francs
  • A tax inspector is prohibited from modifying her own tax liability data
  • An underage subscriber does not have access to an X-rated online movie

Each case above requires that you know exactly what the application is up to, or how the identity of the user making the call relates to the invoked business object. For example, is the user who is making the call the same person as the account owner?

While encouraging declarative security, most security architectures recognize that applications may, at some point, need to implement security policies in code. The CORBA security specification, for example, defines two separate security levels: level 1 for security-unaware applications and level 2 for security-aware applications. For EJB security, however, security awareness is limited to the methods getCallerPrincipal() and isCallerInRole(String roleName), made available to the bean developer by the EJBContext interface. getCallerPrincipal() returns a java.security.Principal instance, which represents the entity making the call (in our case, this will be a user who has logged into the system). isCallerInRole(String roleName) checks whether the entity making the call has been allocated the given role, thus allowing access-control decisions to be made in the code.

Security proxies in JBoss

If you have to apply security policies dependent on your application's business logic, aren't you back to putting code in each bean method? The answer is generally yes. And, as I mentioned before, such a solution proves both fragile and unmanageable. As a solution, JBoss provides a neat alternative to embedding security code directly in your bean implementations: it decouples the security code from the bean code by letting you place the former in a separate, configurable proxy object. In addition to the normal security checks on the caller, JBoss adds an extra security interceptor to check with the bean's security proxy (if one is allocated) whether a particular method invocation should be allowed. You can apply the security checks to the bean as a whole or to individual methods.

Security policy is now enforced outside the bean, and you can implement each defined policy independently of the bean's business code. The JBoss solution separates developer roles, as the security code lives in separate components; in this way, a developer can implement a policy such as "only the account owner can invoke the account bean" by making a simple check of the caller principal against the bean's ID in the proxy. Thereafter, no amount of messing with the bean's code will break the security check.

So what does this buy us?

Security is usually considered a means of protection against malicious attacks -- to defend the system against deliberate attempts by a determined adversary to break into the system and abuse its services. Customized security policies like those described above will help stop such attacks. However, other potential threats may devastate an organization even more, and customized security can also prevent them. One such threat (albeit a mundane one) is the perennial problem of buggy code: it may require the remaining lifetime of the universe to break your transport mechanism's encryption algorithm, but that's no comfort if the wrong person accesses your sensitive data because of some unforeseen error in the code.

As a true example, I sometimes access my phone bill via my telephone company's Website. The site stores my personal contact information, and optionally stores more important details such as my credit card number. Not long ago, I logged in and was greeted with the cheery message, "Welcome back, Fred!" Very nice and friendly, except my name is not Fred. I tried again. "Welcome back, John!" Several more tries produced a different name each time. Further investigation revealed that the personal details were also not, in fact, my own. I called the site's technical support, who confessed that, yes, my details might have been supplied to someone else by accident.

My experience is a typical problem that has plagued many high-profile online applications, often resulting in bad publicity for the companies concerned. Online amateur traders have found themselves in charge of other people's portfolios, and online banking users have been supplied with someone else's account details. While the work of a malicious hacker can potentially be swept under the carpet, normal users will complain bitterly about these kinds of problems, and a flagship product can rapidly turn into a public relations disaster.

You'll find interesting examples of such tales of woe listed in Resources.

Create a JBoss security proxy

Before moving to the example code, let's first look at how to create security proxies in JBoss. A JBoss security proxy can take two forms:

  1. A class instance that implements the org.jboss.security.Proxy interface with two key methods, invokeHome() and invoke(). These are called prior to invoking the bean itself.
  2. An object that implements one or more methods from the bean's home or remote interface. Before calling the method on the bean, the bean container invokes the corresponding method on the proxy.

With the first approach, you merely create a class that implements the required interface, which is an untyped invocation interface particularly convenient for implementing all-or-nothing checks such as those based on the bean ownership. You can use the bean instance, passed as an argument to invoke(), to apply checks that are dependent on the bean state. This option is best if you want to integrate an external security framework or to delegate the decision to a separate access-control service.

Use the second option if you don't want your proxy to have any JBoss code dependencies. The second approach may also be more acceptable if you want your developers to avoid reflection-style APIs; it follows a more natural proxy pattern where the proxy object looks like the business bean.

With the second approach, the container uses a wrapper instance of org.jboss.security.Proxy to delegate calls to your proxy class. By default, the class org.jboss.security.SubjectSecurityProxy is used, but it serves just as an example implementation; in practice, you should supply your own wrapper class. You can use the org.jboss.security.AbstractSecurityProxy class as a base. You also have to provide an org.jboss.security.SecurityProxyFactory instance and configure JBoss to use it to create instances of your proxy on demand. To do so, you supply the SecurityProxyFactoryClassName attribute when setting up the JAASSecurityManager in JBoss's main configuration file, jboss.jcml:

  <!-- JAAS security manager and realm mapping -->
  <mbean code="org.jboss.security.plugins.JaasSecurityManagerService"
      name="Security:name=JaasSecurityManager">
    <attribute name="SecurityManagerClassName">org.jboss.
       security.plugins.JaasSecurityManager</attribute>
    <attribute name="SecurityProxyFactoryClassName">org.jboss.security.
       SubjectSecurityProxy</attribute>
  </mbean>

You then write code in your proxy for each method you wish to protect. In contrast with the first approach, here you don't automatically receive an instance of the invoked bean. To get around this, AbstractSecurityProxy searches for the setBean() method on your delegate class; so, if you must access the bean state, you can implement that method. You will receive a reference to the bean that you can cache similarly to the EJBContext.

Note: AbstractSecurityProxy's current implementation doesn't complain if the invoked method has no matching method on the proxy. Therefore, if someone adds a new method to the bean or changes an existing bean's signature, such actions could silently bypass your access controls. Indeed, the checks will not be applied unless you also change the proxy accordingly. If you wish to enforce strict checking, configure your proxy wrapper class to throw an exception if no matching method is found on the proxy.

Use the proxy

To get JBoss to use your proxy, you specify the class name in the security-proxy element of the jboss.xml configuration file (which contains vendor-specific configuration information and accompanies the standard ejb-jar.xml file for your application). We'll use the classes from the example code as an illustration. If you wish to protect the entity bean TradingAccount, you would code:

  <jboss>
    <!-- use the jboss default "other" security domain -->
    <security-domain>java:/jaas/other</security-domain>
    <enterprise-beans>
      <entity>
        <ejb-name>TradingAccount</ejb-name>
   
        <!-- Specify the proxy class to be used to protect
            this entity --> 
        <security-proxy>com.mkeym.customsec.ejb.
           TradingAccountSecurityProxy</security-proxy>
        ...
      </entity>
    </enterprise-beans>
   
    ...   
  </jboss>

If you include the security-proxy element, the container creates a single instance of the proxy class and uses it to intercept the calls to your beans. All bean instances of this type share the same proxy. The examples use the EJBContext to obtain the name of the principal making the call, so you have to cache the context object in the setEJBContext() method, as you would often do in a typical EJB. However, since the proxy is shared and multiple clients may be simultaneously making calls on different beans, the cached context value may be overwritten before it has been used. The solution is to store it in a java.lang.ThreadLocal object: the proxy methods setEJBContext() and invoke() are called in the same thread so the context value will remain consistent.

Subject-based authorization

If you use the standard JBoss JaasSecurityManager to handle your security, then you can obtain the currently active JAAS Subject as an alternative to calling EJBContext methods. The subject is an instance of javax.security.auth.Subject that is set for the invocation. You can use JNDI (Java Naming and Directory Interface) to obtain a reference to it, as follows:

   ...
   import java.security.Principal;
   import java.security.acl.Group;
   import javax.security.auth.Subject;
   ...
   InitialContext ic = new InitialContext();
   Subject subject = (Subject)ic.lookup("java:comp/env/security/subject");
   // To list the Principals contained in the Subject...
   Iterator principals = subject.getPrincipals().iterator();
   while (principals.hasNext()) {
      Principal p = (Principal)principals.next();
      System.out.println("Principal (" + p.getClass().getName()
          + ") : " + p.getName());
   }
   // To get the roles (the instance of java.security.acl.Group
   // in the list of Principals)
   principals = subject.getPrincipals(java.security.acl.Group.class).iterator();
   if (principals.hasNext()) {
      Group roles = (Group)principals.next();
      Enumeration roleEnum = roles.members();
      while (roleEnum.hasMoreElements()) {
         System.out.println("Role:  " + roleEnum.nextElement());
      }
   }
   

The subject represents a potentially rich source of security context information. Depending on the login module used, you can store many different principal objects in the Subject, and you can access them all, not just one. You also directly access roles defined by the security domain, rather than the logical roles defined by the bean coder, which makes more sense with security proxy code implemented outside the bean and changed independently of it.

A simple example

Consider an example based on a fictitious share-trading application. In this example, I follow the first, untyped proxy approach (i.e., I implement org.jboss.security.Proxy directly). You'll find an alternative typed approach in the source code.

The TradingAccount entity-trading bean features a simple interface with buy() and sell() methods, each of which takes a stock name and a quantity as arguments. The account owner's username (which serves as the bean's primary key) identifies the bean. The bean's balance field accumulates the account's total sales and purchases. Note that the stock's name has no significance to the bean, and I assume, rather unrealistically, that each share has the fixed value of 100 in the local currency. The application employs container-managed persistence to store the two bean fields in the database.

To protect the bean, the TradingAccountSecurityProxy security proxy implements the following simple policies:

  • A trader can buy or sell shares only through her own account
  • A single transaction must have a value less than or equal to 50 million
  • An account's balance cannot exceed 100 million or go below -100 million

The figure below shows the entity bean and the security proxy classes.

Example entity bean and security proxy classes

The invoke() method defines the example code for the above policy items:

public class TradingAccountSecurityProxy implements org.jboss.security.SecurityProxy {
   private Category _log = Category.getInstance(getClass().getName());
   private ThreadLocal _ctx = new ThreadLocal();
 // ... init and invokeHome methods removed
   public void setEJBContext(EJBContext ctx) {
      _log.info("setEJBContext " + ctx);
      _ctx.set(ctx);
   }
   public void invoke(Method m, Object[] args, Object bean)
      throws SecurityException {
      if (!(bean instanceof TradingAccountBean))
         throw new SecurityException("Invalid bean instance for 
            security proxy");
      EJBContext ctx = (EJBContext)_ctx.get();
      String caller = ctx.getCallerPrincipal().getName();
      TradingAccountBean account = (TradingAccountBean)bean;
      String operation = m.getName();
      _log.info("invoke " + operation + " called by " + caller);
     // The implementation of the defined policies.
      if (operation.equals("buy") || operation.equals("sell")) {
         int qty = ((Integer)args[1]).intValue();
     // 1. A trader can only buy or sell shares through her own account.
         if (!account.getAccountId().equals(caller)) {
            _log.warn("method " + operation + " 
               called on account " + account.getAccountId() +
                " by invalid user " + caller);
            throw new SecurityException("caller is not the account owner");
         }
     // 2. A single transaction must have a value
     // less than or equal to 50 million.
         int value = qty * 100; // In our kid-on example all the
                                // shares cost 100.
         if (value > 5e7) {
            _log.warn(operation + " invoked with too high 
               transaction value: " + value);
            throw new SecurityException("transaction value is too large");
         }
     // 3. An account balance cannot exceed 100 million or go below -100
     // million.
         int newBalance = account.getBalance() + (operation.equals
            ("sell") ? value : -value);
         if (Math.abs(newBalance) >= 1e8) {
            _log.warn(operation + " would exceed bounds on account
               balance: " + newBalance);
            throw new SecurityException("balance exceeded");
         }
      }
   }
}

Note the java.lang.ThreadLocal object that caches the EJBContext, enabling you to use it in invoke(). Logging uses the Jakarta log4j package, which is included with JBoss.

Users and roles

The example uses the simple properties file-based security domain, which you configure by supplying the users.properties (containing the users and their passwords) and roles.properties (mapping usernames to roles) files. You'll find both supplied with the example in the /src/resources/ejb subdirectory. I've defined three users: yossarian, orr, and milo, each of whom has the trader role. They all have the same password: password.

Run the example

To run the example, you'll need to install the Jakarta Ant build tool. The build produces an ear file containing the server code and a client jar file containing the command-line client. Here's the process:

  1. Download and install Ant.
  2. Download the latest JBoss/Tomcat distribution (currently JBoss-2.4.3_Tomcat-3.2.3; see Resources for the location). Unpack the archive to a suitable location.
  3. Start JBoss by running the startup script:
    JBoss-2.4.3_Tomcat-3.2.3/jboss/bin/run_with_tomcat
    
  4. Download and unpack the example archive.
  5. Edit the

    build.xml

    file and point the

    dist.root

    property to the location where you unpacked JBoss:

    <property name="dist.root" value="C:/JBoss-2.4.3_Tomcat-3.2.3" />
    

    The build can now locate extra JBoss jar files needed for compilation.

  6. Run Ant in the examples directory, which should produce the server ear file and the client jar. You should see output similar to this:
        C:\customsecurity>ant
        Searching for build.xml ...
        Buildfile: C:\customsecurity\build.xml
        init:
        prepare:
        [mkdir] Created dir: C:\customsecurity\build
        compile:
        [mkdir] Created dir: C:\customsecurity\build\classes\ejb
        [mkdir] Created dir: C:\customsecurity\build\classes\web\WEB-INF\classes
        [mkdir] Created dir: C:\customsecurity\build\classes\client
        [javac] Compiling 4 source files to C:\customsecurity\build\classes\client
        [javac] Compiling 4 source files to C:\customsecurity\build\classes\ejb
        jar:
        [mkdir] Created dir: C:\customsecurity\build\deploy
        [copy] Copying 1 files to C:\customsecurity\build
        [copy] Copying 21 files to C:\customsecurity\build\classes\web
        [jar] Building jar: C:\customsecurity\build\deploy\customsecurity.war
        [copy] Copying 4 files to C:\customsecurity\build\classes\ejb
        [jar] Building jar: C:\customsecurity\build\deploy\customsecurity.jar
        [jar] Building jar: C:\customsecurity\customsecurity-client.jar
        [mkdir] Created dir: C:\customsecurity\build\ear
        [copy] Copying 1 files to C:\customsecurity\build\ear
        [copy] Copying 1 files to C:\customsecurity\build\ear
        [copy] Copying 1 files to C:\customsecurity\build\ear
        [jar] Building jar: C:\customsecurity\customsecurity.ear
        BUILD SUCCESSFUL
    
  7. Deploy the application by copying the ear file to the deploy directory within the JBoss distribution (JBoss-2.4.3_Tomcat-3.2.3/jboss/deploy). If JBoss is running, you should see output from the automatic deployer as it sets up your application.
  8. Run the command-line client with the script provided in the example directory. If you use the script, point the environment variable JBOSS_HOME to the jboss directory (JBoss-2.4.3_Tomcat-3.2.3/jboss). The script can now find classes the client needs.

Now that you've set everything, you can try the example application. First, run the client with a username, password, and the init parameter, which creates the account entity EJBs for each user:

  C:\customsecurity>client yossarian password init
  Created LoginContext
  Login completed
  Created accounts successfully.

Then try to buy or sell through an account. The client then takes additional arguments for the account name to trade on, stock name, and share quantity in the transaction:

  C:\customsecurity>client yossarian password buy yossarian CRP 500
  Created LoginContext
  Login completed
  'Buy' successful on account yossarian, balance is now -50000

You should see corresponding messages from the security proxy in the server output.

However, if you then put a transaction through on a different account, the security proxy will prevent such a move:

  C:\customsecurity>client yossarian password buy orr CRP 500
  Created LoginContext
  Login completed
  Buy failed: RemoteException occurred in server thread; nested exception is:
  javax.transaction.TransactionRolledbackException: SecurityProxy.invoke failure;

... and the server will log the failure:

  [TradingAccountSecurityProxy] invoke buy by yossarian
  [TradingAccountSecurityProxy] method buy called on account orr by invalid user yossarian
  [Default] SecurityProxy.invoke exception, principal=yossarian
  [TradingAccount] TRANSACTION ROLLBACK EXCEPTION:SecurityProxy.invoke failure; nested exception is:
  java.lang.SecurityException: SecurityProxy.invoke exception, principal=yossarian;    

The proxy prevents Yossarian from buying shares through Orr's account. You can test the other policies in a similar fashion; try to overrun the allowed balance limits or put through an excessively large transaction.

Web interface

I've also included an easy-to-use Web client that uses form-based authentication and employs the Struts framework as an alternative to the command-line client (run it once to create the accounts). Just point your browser to http://127.0.0.1:8080/customsec after you've deployed the application. You can log in as any defined user, list the accounts and their balances, and attempt to buy or sell fixed amounts (100,000 shares) through any account. Notice that the currently logged-in user's name shows up on the title bar. Obviously the security proxy should stop you from using either of the other two accounts.

Things to try

Now that you've got a hang of things, consider:

  • Modifying the existing code to use the active Subject instead of accessing security information through the EJBContext. I've included code showing how to obtain the active subject, as well as the principals and roles it contains.
  • Switching to the typed proxy, called TypedSecurityProxy. Change the entry in jboss.xml that specifies the proxy class. The methods are left empty (apart from logging), so you can implement them as you choose.

Points to note

Finally, as you employ security proxies, keep the following important points in mind:

  • Use the standard declarative security in preference to custom code: Only use application-specific custom access-control code when you know that standard, container-managed mechanisms cannot enforce your authorization policies.
  • Keep it simple: Simple controls are easier to maintain and are less likely to cause a performance hit (remember that the proxy will be called prior to every invocation you make on your bean). Maintaining regularly changing access policies may be a nontrivial process, and you must decide whether the extra development and testing effort is worth it.
  • Custom security as a bug detector: Most problems we've discussed shouldn't occur during normal usage, but the custom security checks provide a reassuring safeguard against coding errors. If your main concern is protecting against buggy code, you may relax your security proxies' use once your application stabilizes. The extra calls' overhead will generally be minimal, however, so you can probably leave them in place.
  • Calling the remove() method on EJBHome: The invocation mechanism in JBoss means that removing an entity bean through the home interface is always converted to a call to remove() on the object itself. As a result, the invokeHome() method on your security proxy will not be called. If you want to place specific controls on deleting your entities, you should put them in the invoke() method only.
  • Nonstandard mechanism: The security proxies are JBoss-specific, so they are not directly portable to other application servers. You could conceivably implement a proxy framework using standard EJBs, but this would be inefficient and cumbersome to maintain. Of course, your EJBs can still be deployed unmodified on other servers without using the proxies, but they will be less secure.

For EJBs, security matters

You've seen that customized security proves a useful enhancement to standard EJB security, allowing additional, application-specific access-control checks beyond the standard role-based model's scope. If you implement such access-control policies, your application will not only be less vulnerable to deliberate attacks, but will also be more robust in general.

A security proxy provides a single point of access to the component, through which all external calls must pass, making it an ideal place to implement any constraints that you absolutely do not want violated -- access controls, checks on data, or whatever else you need. It allows the security-specific code that implements your authorization policies to evolve separately from your business code, so you don't have to put security code directly in your beans. This in turn allows the separation of business and security programmer roles in a project, provides for easier maintenance, and gives you greater flexibility in how you implement your controls.

Luke Taylor is an independent consultant based in Glasgow, Scotland. He obtained a PhD in theoretical nuclear physics from Glasgow University and subsequently worked in London in software development and as a consultant specializing in Java, CORBA, and security technologies. He recently founded Monkey Machine, which offers services in Java, J2EE, CORBA, and security with a particular focus on open source implementations, such as JBoss.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more