Integrate security infrastructures with JBossSX

JBossSX uses JAAS to integrate application servers and security infrastructures

1 2 3 4 5 Page 3
Page 3 of 5
  1. The client first has to perform a JAAS login to establish the principal and credentials for authentication, which is how clients establish their login identities in JBoss. Support for presenting the login information via the JNDI InitialContext properties is not provided. A JAAS login entails creating a javax.security.auth.login.LoginContext instance and passing the configuration's name to use. In Figure 6, the configuration name is other. This one-time login associates the login principal and credentials with all subsequent EJB method invocations. Note that the process might not authenticate the user. The nature of the client-side login depends on the login module configuration that the client uses. In Figure 6, the other client-side login configuration entry is set up to use the ClientLoginModule (org.jboss.security.ClientLoginModule); that module does not perform client-side authentication, but simply binds the username and password to the JBoss EJB invocation layer for later authentication on the server.
  2. Later, the client obtains an EJB's home interface and attempts to create a bean, resulting in a home interface method invocation sent to the JBoss server. The invocation includes the method arguments passed by the client along with the user identity and credentials from the client-side JAAS login.
  3. On the server side, the security check first requires authentication of the user invoking the call, which, as on the client side, involves a JAAS login. The security domain under which the EJB is secured determines the choice of login modules. The security domain's name is used as the login configuration entry name passed to the LoginContext constructor. In Figure 6, the EJB security domain is jwdomain. If the JAAS login authenticates the user, a JAAS Subject is created that contains the following in its PrincipalsSet:
    • A java.security.Principal that corresponds to the client identity.
    • A java.security.acl.Group named Roles that contains the role names from the application domain to which the user has been assigned. org.jboss.security.SimplePrincipal objects are used to represent the role names; SimplePrincipal is a simple string-based implementation of Principal. These roles are used to validate the roles assigned to methods in ejb-jar.xml and the EJBContext.isCallerInRole(String) method.
    • An optional java.security.acl.Group named CallerPrincipal, which contains a single org.jboss.security.SimplePrincipal that corresponds to the identity of the application domain's caller. The CallerPrincipal sole group member is the value returned by the EJBContext.getCallerPrincipal() method. The purpose of this mapping is to allow a Principal known in the deployment security environment to map to a Principal with a name known to the application. In the absence of a CallerPrincipal mapping the deployment security environment, Principal returns.
  4. The final step of the security check is to verify the authenticated user has permission to invoke the requested method. Doing this requires the following steps:
    1. Obtain the names of the roles allowed to access the EJB method from the EJB container. The names are ejb-jar.xml descriptor role-name elements of all method-permission elements containing the invoked method.
    2. If no roles have been assigned, then access to the method is denied. Otherwise, the JaasSecurityManager.doesUserHaveRole(Principal, Set) method invokes to see if the caller principal has one of the assigned role names. The method iterates through the role names and checks if the user javax.security.auth.Subject's Roles group contains a SimplePrincipal with the assigned role name. Access is allowed if any role name is a member of the Roles group; access is denied if none of the role names are members.
    3. If the EJB was configured with a custom security proxy, the method invocation is delegated to it. If the security proxy wants to deny access to the caller, it will throw a SecurityException. If no SecurityException is thrown, access to the EJB method is allowed; the SecurityInterceptor passes the method invocation to the next container interceptor.

Note that you can configure the

JaasSecurityManager

to use a cache of authentication information so that a JAAS login is not performed on every method invocation. If no cache is specified in the

JaasSecurityManager

's configuration, a timed cache is used by default.

JBossSX custom login modules

As shown above, the JBossSX security manager relies on the JAAS login modules for a security domain for authentication and authorization. The security manager authorizes users with the information stored in the

Subject

. If the

LoginModule

s that ship with the JBoss server do not integrate into your existing security infrastructure, you can write a custom

LoginModule

that does. Writing a custom login module entails mapping your security infrastructure information onto a

javax.security.auth.Subject

using the usage pattern expected by JBossSX.

JBossSX Subject usage patterns

The JBossSX security manager inside the JBoss application server executes the login modules used by a security domain. Since these modules run inside the JBoss server, they are called

server-side login modules.

When you write a custom

LoginModule

, you write a server-side login module. To understand how server-side login modules are used in JBoss, you need to understand the JAAS

Subject

class's information storage features. You can obtain security information associated with a

Subject

in six ways:

  • java.util.Set getPrincipals()
  • java.util.Set getPrincipals(java.lang.Class c)
  • java.util.Set getPrivateCredentials()
  • java.util.Set getPrivateCredentials(java.lang.Class c)
  • java.util.Set getPublicCredentials()
  • java.util.Set getPublicCredentials(java.lang.Class c)

For

Subject

identities and roles, JBossSX has selected the most natural choice: the

PrincipalsSet

obtained via

getPrincipals()

and

getPrincipals(java.lang.Class)

. The usage pattern follows: User identities (username, social security number, employee ID, and so on) are stored as

java.security.Principal

objects in the

Subject Principals

set. The assigned user roles are also stored in the

Principal

s set, but in named role sets using

java.security.acl.Group

instances. The

Group

interface -- a collection of

Principal

s and/or

Group

s -- is a subinterface of

java.security.Principal

. Any number of role sets can be assigned to a

Subject

. Currently, the JBossSX framework uses two well-known role sets:

Roles

and

CallerPrincipal

. The

Roles

set is the

Principal

set for the named roles as known in the application domain under which the

Subject

has been authenticated. This role set is used by methods like the

EJBContext.isCallerInRole(String)

, which EJBs use to see if the current caller belongs to the named application domain role. The security interceptor logic that performs method permission checks also uses the role set. The

CallerPrincipal

role set consists of the single

Principal

identity assigned to the user in the application domain. The

EJBContext.getCallerPrincipal()

method uses

CallerPrincipal

to allow the application domain to map from the operation environment identity to a user identity suitable for the application. If a

Subject

does not have a

CallerPrincipal

role set, the application identity is the operational environment identity.

Support for the Subject usage pattern

To simplify correct implementation of the

Subject

usage patterns described in the preceding section, JBossSX includes an abstract login module that handles the population of the authenticated

Subject

with a template pattern. The

org.jboss.security.auth.spi.AbstractLoginModule

class provides concrete implementations of the

javax.security.auth.spi.LoginModule

interface and offers abstract methods for the key tasks specific to an operation environment security infrastructure. The key details of the class are:

 package org.jboss.security.auth.spi;
 
 /** This class implements the common functionality required for a JAAS
 server-side LoginModule and implements the JBossSX standard Subject usage
 pattern of storing identities and roles. Subclass this module to create your
 own custom LoginModule and override the login(), getRoleSets(), and getIdentity()
 methods.
 */
 public abstract class AbstractServerLoginModule
     implements javax.security.auth.spi.LoginModule
 {
     protected Subject subject;
     protected CallbackHandler callbackHandler;
     protected Map sharedState;
     protected Map options;
 
 ...
     /** Initialize the login module. This stores the subject, callbackHandler
         and sharedState, and options for the login session. Subclasses should override
         if they need to process their own options. A call to super.initialize(...)
         must be made in the case of an override.
     @param subject, the Subject to update after a successful login.
     @param callbackHandler, the CallbackHandler that will be used to obtain the
         the user identity and credentials.
     @param sharedState, a Map shared between all configured login module instances
     @param options,
         @option password-stacking: if true, the login identity will be taken from the
         javax.security.auth.login.name value of the sharedState map, and
         the proof of identity from the javax.security.auth.login.password
         value of the sharedState map.
     */
     public void initialize(Subject subject, CallbackHandler callbackHandler,
        Map sharedState, Map options)
     {
        ...
     }
 
     /** Looks for javax.security.auth.login.name and javax.security.auth.login.password
         values in the sharedState map if the useFirstPass option was true and returns
         true if they exist. If they do not or are null, this method returns false.
         Subclasses should override to perform the required credential validation steps.
     */
     public boolean login() throws LoginException
     {
        ...
     }
 
     /** Overridden by subclasses to return the Principal that corresponds to
      the user primary identity.
     */
     abstract protected Principal getIdentity();
     /** Overridden by subclasses to return the Groups that correspond 
         to the role sets assigned to the user. Subclasses should create at
         least a Group named "Roles" that contains the roles assigned to the user.
         A second common group is "CallerPrincipal," which provides the application
         identity of the user rather than the security domain identity.
     @return Group[] containing the sets of roles 
     */
     abstract protected Group[] getRoleSets() throws LoginException;
 
 }

As an alternate base login module, the

UsernamePasswordLoginModule

further simplifies custom login module implementation by using the string-based username as the user identity and the

char[]

password as the authentication credential. It also maps anonymous users (indicated by a null username and password) to a

Principal

. You would typically support anonymous users by setting the

Principal

that

EJBContext.getCallerPrincipal()

returns when an unsecure servlet or JavaServer Page (JSP) calls an EJB. The key methods of

UsernamePasswordLoginModule

are:

 package org.jboss.security.auth.spi;
 
 /** An abstract subclass of AbstractServerLoginModule that imposes a
  an identity == String username, credentials == String password view on
  the login process. Subclasses override the getUsersPassword()
  and getUsersRoles() methods to return the expected password and roles
  for the user.
  */
 public abstract class UsernamePasswordLoginModule
     extends AbstractServerLoginModule
 {
    /** The login identity */
    private Principal identity;
    /** The proof of login identity */
    private char[] credential;
    /** The principal to use when a null username and password are seen */
    private Principal unauthenticatedIdentity;
 
 ...
 
    /** Override the superclass method to look for an unauthenticatedIdentity
     property. This method first invokes the super version.
     @param options,
     @option unauthenticatedIdentity: the name of the principal to assign
     and authenticate when a null username and password are seen.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler,
       Map sharedState, Map options)
    {
       super.initialize(subject, callbackHandler, sharedState, options);
       // Check for unauthenticatedIdentity option.
       String name = (String) options.get("unauthenticatedIdentity");
       if( name != null )
          unauthenticatedIdentity = new SimplePrincipal(name);
    }
 
 ...
 
    /** A hook that allows subclasses to change the validation of the input
     password against the expected password. This version checks that
     neither inputPassword or expectedPassword are null and that 
     inputPassword.equals(expectedPassword) is true;
     @return true if the inputPassword is valid, false otherwise.
     */
    protected boolean validatePassword(String inputPassword, String expectedPassword)
    {
       if( inputPassword == null || expectedPassword == null )
          return false;
       return inputPassword.equals(expectedPassword);
    }
 
    /** Get the expected password for the current username available via
     the getUsername() method. This is called from within the login()
     method after the CallbackHandler has returned the username and
     candidate password.
     @return the valid password String
     */
    abstract protected String getUsersPassword() throws LoginException;
 }

Write a custom login module

When writing a custom login module that integrates with your security infrastructure, you should start by subclassing

org.jboss.security.auth.spi.AbstractLoginModule

or one of its subclasses to ensure that your login module provides the authenticated

Principal

information in the form expected by the JBossSX security manager. When subclassing the

AbstractLoginModule

, you need to override:

  • void initialize(Subject, CallbackHandler, Map, Map) if you have custom options to parse.
  • boolean login() to perform the authentication activity.
  • Principal getIdentity() to return the Principal object for the user authenticated by the log() step.
  • Group[] getRoleSets() to return at least a Group named Roles that contains the roles assigned to the Principal authenticated during login(). A second common group is CallerPrincipal; it provides, if necessary, the user's application identity rather than the security domain identity.

When subclassing the

UsernamePasswordLoginModule

, a subclass of

AbstractLoginModule

, write:

  • void initialize(Subject, CallbackHandler, Map, Map) if you have custom options to parse.
  • String getUsersPassword() to return the expected password for the current username available via the getUsername() method. The getUsersPassword() method is called from within login() after the CallbackHandler returns the username and candidate password.
  • Group[] getRoleSets() to return at least a Group named Roles that contains the roles assigned to the Principal authenticated during login(). A second group CallerPrincipal provides, if necessary, the user's application identity instead of the security domain identity.

An example

Want to see the security features in action? Download the source code from

Resources

. I'll walk you through it using the download archive to set up and deploy the example to a JBoss/Tomcat 2.4 bundle. You'll also look at the application from the secure and unsecured servlet accessing a secured EJB to demonstrate many of the security features presented in this article. To deploy the example application:

1 2 3 4 5 Page 3
Page 3 of 5