Integrate security infrastructures with JBossSX

JBossSX uses JAAS to integrate application servers and security infrastructures

A key aspect of the Java 2 Platform, Enterprise Edition (J2EE) component models is a simple declarative security model. The Enterprise JavaBean (EJB) 2.0 and Servlet 2.2 specifications support a role-based declarative security model that externalizes security from application logic and decouples the application security roles from the deployment environment's security implementation. At the application level, the

ejb-jar.xml

and

web.xml

deployment descriptors define security. Although this model allows for an independent, simple specification of the application server's security requirements, mapping the application-defined security onto the deployment environment security's infrastructure is an application-server-specific activity. Thus, configuring a J2EE application's security requires proprietary application server APIs or tools. One such tool is the Java Authentication and Authorization Service (JAAS). In this article, I describe how the JBoss security extension, JBossSX, uses the standard JAAS to integrate with the deployment environment's security infrastructure. By configuring the JAAS login modules bundled with JBoss, you can complete the integration without custom programming. If the bundled login modules don't work with your security infrastructure, you can simply write a custom login module that does; I'll show you how in this article. The key topics that I cover here include:

  • The J2EE declarative security model
  • The key JAAS classes
  • Details of how the JBossSX security manager uses JAAS in its implementation of the J2EE declarative security model
  • Details of how you can write a custom JAAS login module for JBoss

I also include a secure enterprise application example that demonstrates the declarative security model and the configuration of JAAS login modules to integrate the deployment environment's security.

J2EE declarative security overview

The first step to securing a J2EE application is to specify the application security requirements via the standard J2EE deployment descriptors. You secure access to EJBs and Web components in an enterprise application by using the

ejb-jar.xml

and

web.xml

deployment descriptors. Figures 1 and 2 illustrate the security-related elements in the EJB 2.0 and Servlet 2.2 deployment descriptors, respectively.

Figure 1. The EJB 2.0 deployment descriptor security elements Click on thumbnail to view full-size image.

Together, these security elements define the bean author and application assembler's view of an enterprise application's security requirements.

Figure 2. The Servlet 2.2 deployment descriptor security elements Click on thumbnail to view full-size image.

So that you'll have sufficient background for the example presented later in this article, I'll first review the security elements pictured in Figures 1 and 2.

Enterprise beans security references

As you can see in Figure 1, enterprise beans may declare one or more

security-role-ref

elements. An EJB can access the caller principal and ask if the caller belongs to a role by name. The caller principal is obtained from the

EJBContext.getCallerPrincipal()

method as a

java.security.Principal

instance. Using the

EJBContext.isCallerInRole(String)

method, an EJB checks if a caller is in a role that has been declared with a

security-role-ref

element. The

role-name

element value must link to a security role in the assembly-descriptor section of

ejb-jar.xml

through the

role-link

element. You typically use

isCallerInRole()

to perform a security check that cannot be defined using method permissions. See section 21.2.5 of the

EJB 2.0 Specification PFD2

for more details on accessing the caller's security context.

Enterprise beans security identity

Figure 1 also shows that enterprise beans can optionally declare a

security-identity

element. New to EJB 2.0 is the ability to specify what identity an EJB should use when it invokes methods on other EJBs. The application assembler uses the

security-identity

element to indicate that the current caller's identity should be propagated by using a

use-caller-identity

element as

security-identity

's value. Alternatively, the application assembler can use the

run-as

element with the

security-identity

's value as

role-name

to specify that EJB calls are performed with the security role given by the

role-name

value. Note that this does not change the caller's identity as seen by

EJBContext.getCallerPrincipal()

. Rather, the caller's security roles are set to the single role specified by the

run-as/role-name

element value. You can use a

run-as

identity to keep external clients from accessing internal EJBs. To do that, assign the internal EJB method permissions that restrict access to a role never assigned to an external client, and use the restricted role as the

run-as/role-name

element value for EJBs that use the internal EJB.

Assembly descriptor security roles

The security role referenced by either

security-role-ref

or

security-identity

elements needs to map to one of the application's declared roles. An application assembler defines logical security roles by adding

security-role

elements to the

assembly-descriptor

element. In JBoss, a

security-role

is only used to map an EJB

security-role-ref/role-name

to the logical role to which the EJB role name refers. The user's assigned roles are a dynamic function of the application's security manager, as you will see when I discuss the JBossSX implementation. JBoss does not require defined

security-role

s to identify method permissions. Therefore, you should specify a

security-role

element for every role used in the

method-permission

element for portability across application servers and for deployment descriptor maintenance.

Assembly descriptor method permissions

An application assembler can set the roles that are allowed to invoke an EJB's home and component interface methods through

method-permission

elements. Each

method-permission

element contains one or more

role-name

elements that define the logical roles allowed access to one or more EJB methods as identified by

method

elements. With EJB 2.0, you can now specify the

unchecked

element instead of the

role-name

element to declare that an authenticated user can access one or more methods. In addition, you can declare that no one should have access to a method with the

exclude-list

element. For method syntax, see section 21.3.2 of the

EJB 2.0 Specification PFD2

.

An example EJB deployment descriptor

The following

ejb-jar.xml

descriptor illustrates the use of EJB security elements and is the descriptor used in the article example:

 <ejb-jar>
   <display-name>SecurityTests</display-name>
   <enterprise-beans>
     <session>
       <description>A trivial stateless session echo bean</description>
       <ejb-name>PublicSession</ejb-name>
       <home>org.jboss.docs.jaas.howto.SessionHome</home>
       <remote>org.jboss.docs.jaas.howto.Session</remote>
       <ejb-class>org.jboss.docs.jaas.howto.StatelessSessionBean</ejb-class>
       <session-type>Stateless</session-type>
       <transaction-type>Container</transaction-type>
       <ejb-ref>
         <ejb-ref-name>ejb/PrivateSession</ejb-ref-name>
         <ejb-ref-type>Session</ejb-ref-type>
         <home>org.jboss.docs.jaas.howto.SessionHome</home>
         <remote>org.jboss.docs.jaas.howto.Session</remote>
         <ejb-link>PrivateSession</ejb-link>
       </ejb-ref>
       <security-identity>
         <run-as>
           <role-name>InternalUser</role-name>
         </run-as>
       </security-identity>
     </session>
 
     <session>
       <description>A trivial stateful session echo bean</description>
       <ejb-name>PrivateSession</ejb-name>
       <home>org.jboss.docs.jaas.howto.SessionHome</home>
       <remote>org.jboss.docs.jaas.howto.Session</remote>
       <ejb-class>org.jboss.docs.jaas.howto.StatefulSessionBean</ejb-class>
       <session-type>Stateful</session-type>
       <transaction-type>Container</transaction-type>
     </session>
   </enterprise-beans>
 
   <assembly-descriptor>
     <security-role>
       <role-name>Coder</role-name>
     </security-role>
     <security-role>
       <role-name>Echo</role-name>
     </security-role>
     <security-role>
       <role-name>InternalUser</role-name>
     </security-role>
 
     <method-permission>
       <role-name>Echo</role-name>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>*</method-name>
       </method>
     </method-permission>
     <method-permission>
       <role-name>InternalUser</role-name>
       <method>
         <ejb-name>PrivateSession</ejb-name>
         <method-name>*</method-name>
       </method>
     </method-permission>
     <method-permission>
       <role-name>Coder</role-name>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>create</method-name>
       </method>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>remove</method-name>
       </method>
     </method-permission>
     <method-permission>
       <unchecked/>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>noop</method-name>
       </method>
     </method-permission>
 
     <exclude-list>
       <description>Methods that cannot be used in this
         deployment</description>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>restricted</method-name>
       </method>
     </exclude-list>
   </assembly-descriptor>
 </ejb-jar>

Web application security constraints

In a Web application, security is defined by the roles allowed access to content; a URL pattern identifies the protected content. For example, the

web.xml

descriptor fragment below indicates that any URL lying under the Web application's

/restricted

path requires an

AuthorizedUser

role:

 <web-app>
 ...
   <security-constraint>
     <web-resource-collection>
       <web-resource-name>Secure Content</web-resource-name>
       <url-pattern>/restricted/*</ url-pattern></
     <web-resource-collection>
     <auth-constraint>
       <role-name>AuthorizedUser</role-name>
     </auth-constraint>
   </security-constraint>
 ...
   <security-role>
      <description>The role required to access restricted content</description>
      <role-name>AuthorizedUser</role-name>
   </security-role>
 </web-app>

The

security-role-ref

and

security-role

elements are equivalent to their EJB counterparts.

Specify the security domain in JBoss

The J2EE security elements I have covered describe only the security requirements from the application's perspective. Since J2EE security elements declare logical roles, the application deployer maps the roles from the application domain onto the deployment environment. The EJB specification omits these application-server-specific details. In JBoss, mapping the application roles onto the deployment environment entails specifying a security manager that implements the J2EE security model. Figure 3 shows the JBoss-specific EJB and Web application deployment descriptor's security-related elements.

Figure 3. The JBoss-specific EJB and Web deployment descriptor security elements Click on thumbnail to view full-size image.

Use the

security-domain

element to identify the Java Naming and Directory Interface (JNDI) name of the security manager interface implementation that JBoss uses for the EJB and Web containers. You may specify

security-domain

as an EJB-level element to only set security for one or more EJBs when

security-domain

is not a top-level element, or when

security-domain

is a top-level element and you want to override it. The

security-proxy

element identifies a custom security interceptor that allows per-request security checks outside the scope of the EJB declarative security model without embedding security logic into the EJB implementation. I won't go into detail about that JBoss-specific feature, as this article focuses on using JAAS to implement the standard declarative security model. A JBoss security manager implementation requires the

org.jboss.security.EJBSecurityManager

and

org.jboss.security.RealmMapping

interfaces presented in Figure 4.

Figure 4. JBoss security manager interfaces Click on thumbnail to view full-size image.

For the remainder of this article, I focus on the JBossSX org.jboss.security.plugins.JaasSecurityManager, a JAAS-based implementation of the security manager interfaces. But first, I'll present a brief introduction to JAAS.

What is JAAS?

The JAAS 1.0 API consists of a set of Java packages for the user authentication and authorization. It implements a Java version of the standard Pluggable Authentication Module (

PAM

) framework and compatibly extends the Java 2 Platform's access control architecture to support user-based authorization. JAAS was first released as an extension package for JDK 1.3 and is bundled with the current JDK 1.4 beta. As the JBossSX framework only uses the authentication aspect of JAAS to implement the declarative-role-based J2EE security model, this introduction focuses only on that topic. For a more comprehensive overview, see the JAAS material in

Resources

. I took much of this section's material from the

JAAS 1.0 Developers Guide

. JAAS authentication is performed in a pluggable fashion. This permits Java applications to remain independent from underlying authentication technologies and allows the JBossSX security manager to work in different security infrastructures. Integration with a security infrastructure can succeed without changing the JBossSX security manager. Authentication consists of instantiating a

LoginContext

object, which passes the name of a

Configuration

entry to determine which

LoginModule

will be created. The

LoginModule

s define the authentication technology.

LoginModule

s often use a username and password to verify identity, but any form of identity and identity verification will work.

The JAAS core classes

The JAAS core classes break down into three categories:

common, authentication,

and

authorization.

The list below presents the common and authentication classes, since the JBossSX functionality covered in this article uses only these classes.

  • Common classes:
    • Subject (javax.security.auth.Subject)
    • Principal (java.security.Principal)
  • Authentication classes:
    • Callback (javax.security.auth.callback.Callback)
    • CallbackHandler (javax.security.auth.callback.CallbackHandler)
    • Configuration (javax.security.auth.login.Configuration)
    • LoginContext (javax.security.auth.login.LoginContext)
    • LoginModule (javax.security.auth.spi.LoginModule)

Subject and Principal

To authorize access to resources, applications first need to authenticate the request's source. The JAAS framework defines the term

Subject

to represent the request's source.

Subject

is the key JAAS class; it represents information for a single entity, such as a person or service. It encompasses the entity's principals, public credentials, and private credentials. The JAAS APIs use the existing Java 2

java.security.Principal

interface to represent a principal. Once authenticated, a

Subject

is populated with associated identities, or

Principal

s. A

Subject

may have many

Principal

s. For example, a person may have a name

Principal

(John Doe) and a social security number

Principal

(123-45-6789), both of which help distinguish it from other

Subject

s. To retrieve the

Principal

s associated with a

Subject

, two methods are available:

     public Set getPrincipals();
     public Set getPrincipals(Class c);

The first method returns all

Principal

s contained in the

Subject

. The second method only returns those

Principal

s that are instances of

Class c

or

Class c

's subclasses. An empty set will return if the

Subject

has no associated

Principal

s. Note that the

java.security.acl.Group

interface is a subinterface of

java.security.Principal

, and so an instance in the

Principal

s set may represent a logical grouping of other principals or groups of principals.

Authentication classes

The following steps will authenticate a

Subject

:

  1. An application instantiates a LoginContext.
  2. The LoginContext consults a Configuration to load all the LoginModules configured for that application.
  3. The application invokes the LoginContext's login method.
  4. The login method invokes all the loaded LoginModules. Each LoginModule attempts to authenticate the Subject. Upon success, LoginModules associate relevant Principals and credentials with the Subject.
  5. The LoginContext returns the authentication status to the application.
  6. If authentication succeeds, the application retrieves the authenticated Subject from the LoginContext.

The

LoginContext

class provides the basic methods for authenticating

Subject

s and offers a way to develop an application independent of the underlying authentication technology. The

LoginContext

consults a

Configuration

to determine the authentication services -- or

LoginModule

s -- configured for a particular application. Therefore, you can plug in different

LoginModule

s under an application without changing the application itself. All

LoginContext

constructors share a common parameter:

name

. The

LoginContext

uses

name

to index the login

Configuration

. Actual authentication occurs with a call to the following method:

     public void login() throws LoginException;

When

login()

invokes, all the configured

LoginModule

s' respective login methods invoke to perform authentication. If authentication succeeds, the following method can retrieve the authenticated

Subject

(which may now hold

Principal

s, public credentials, and private credentials):

      public Subject getSubject();

To log out a

Subject

and remove its authenticated

Principal

s and credentials, use the method below:

     public void logout() throws LoginException;

The following code sample demonstrates the calls necessary to authenticate a

Subject

using the

LoginModule

s configured under the name

jwdomain

:

     // let the LoginContext instantiate a new Subject
     LoginContext lc = new LoginContext("jwdomain");
     try
     {
         // authenticate the Subject
         lc.login();
         System.out.println("authentication successful");
 
         // get the authenticated Subject
         Subject subject = lc.getSubject();
 
         ...
 
         // all finished -- logout
         lc.logout();
     }
     catch (LoginException le)
     {
         System.out.println("authentication unsuccessful");
         le.printStackTrace();
     }

With the

LoginModule

interface, developers can implement different authentication technologies that can be plugged under an application. For example, one

LoginModule

may perform username/password-based authentication, while others may interface to hardware devices such as smart card readers or biometric authenticators. To instantiate a

LoginModule

, a

LoginContext

expects each

LoginModule

to provide a public constructor that takes no arguments. Then, to initialize a

LoginModule

with the relevant information, a

LoginContext

calls the

LoginModule

's

initialize()

method. The

subject

argument is guaranteed to be nonnull. The signature of the

initialize()

method is:

    void initialize(Subject subject, CallbackHandler callbackHandler,
        Map sharedState, Map options);

The

login()

method starts authentication; its signature is:

      boolean login() throws LoginException;

An example method implementation might prompt the user for a username and password, and then verify the information against data stored in a naming service such as NIS or LDAP. Alternative implementations might interface to smart cards and biometric devices or simply extract user information from the underlying operating system. The validation of user identity by each

LoginModule

is considered Phase 1 of JAAS authentication. Authentication completes when the

LoginContext

calls:

      boolean commit() throws LoginException;

If Phase 1 succeeds, then the

commit()

method continues with Phase 2: associating relevant

Principal

s, public credentials, or private credentials with the

Subject

. If Phase 1 fails, then

boolean commit() throws LoginException;

removes any previously stored authentication state, such as usernames or passwords. The following method halts authentication:

     boolean abort() throws LoginException;

Typical implementations of that method also clean up previously stored authentication state. The following method logs out a

Subject

:

     boolean logout() throws LoginException;

That method removes the

Principal

s and credentials originally associated with the

Subject

during the commit operation. Credentials should be destroyed upon removal. When a

LoginModule

must communicate with the user to obtain authentication information, it uses a

CallbackHandler

. Applications implement the

CallbackHandler

interface and pass it to the

LoginContext

, which forwards it directly to the underlying

LoginModules

.

LoginModule

s use the

CallbackHandler

both to gather input from users (such as a password or smart-card PIN number) and to supply information to users (such as status information). By allowing the application to specify the

CallbackHandler

, underlying

LoginModule

s remain independent from the different ways applications interact with users. For example, a

CallbackHandler

's implementation for a GUI application might display a window to solicit user input. On the other hand, a

CallbackHandler

's implementation for a non-GUI environment, such as an application server, might simply obtain credential information using an application server API. The

CallbackHandler

interface has one method to implement:

      void handle(Callback[] callbacks)
        throws java.io.IOException, UnsupportedCallbackException;

The last authentication class I will cover is the

Callback

interface. It is a tagging interface that features several implementations, including

NameCallback

and

PasswordCallback

.

LoginModule

s use a

Callback

to request information required by the authentication mechanism the

LoginModule

encapsulates.

LoginModule

s pass an array of

Callback

s directly to the

CallbackHandler

's handle method during the authentication's login phase.

Inside the JBossSX JaasSecurityManager

Now let's return to the JBossSX security manager implementation. I'll start with an overview of how the JBoss security components interact. Figure 5 shows the main components and how the security domain assigned in the JBoss EJB and Web deployment descriptors ties them together.

Figure 5. Security component interaction overview Click on thumbnail to view full-size image.

Figure 5 depicts an enterprise application that contains both EJBs and Web content secured under the security domain

jwdomain

. The EJB and Web containers have a request interceptor architecture that includes a security interceptor, which enforces the container security model. At deployment time, the security domain in the

jboss.xml

and

jboss-web.xml

descriptors is used to obtain the security manager instance associated with the container and used by the security interceptor. When a secured component is requested, the security interceptor delegates security checks to the security manager instance associated with the container. For the JBossSX default security manager implementation, shown in Figure 5 as the

JaasSecurityMgr

component, security checks are based on the information associated with the

javax.security.auth.Subject

instance. That instance results from executing the JAAS login modules configured under the name matching the

security-domain

element value. We will drill into the

JaasSecurityManager

implementation and its use of JAAS in the following sections.

The JAAS in JaasSecurityManager

The default JBossSX security manager implementation

org.jboss.security.plugins.JaasSecurityManager

uses the JAAS packages to implement its behavior. In particular, its behavior derives from the login module (

javax.security.auth.spi.LoginModule

) whose configuration entry name matches the security domain to which the

JaasSecurityManager

has been assigned. The login module implements the security domain's principal authentication and role-mapping behavior. Hence, you can use the

JaasSecurityManager

across different security domains simply by plugging in different login module configurations for the domains.

The security check

Consider a client that invokes a method on an EJB configured to use a

JaasSecurityManager

instance for security. Figure 6 illustrates some components involved in the security check; the security check's key steps are outlined below the figure.

Figure 6. EJB access authentication procedure Click on thumbnail to view full-size image.
  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:

  • You need the Ant build tool. If you don't already have it, see the Ant homepage.
  • Next, download the JBoss/Tomcat 2.4.x bundle from Sourceforge and unarchive it. At the time of this writing, the current JBoss/Tomcat bundle is JBoss-2.4.0.26_Tomcat-3.2.3.zip; it unarchives to create a JBoss-2.4.0_Tomcat-3.2.3 directory.
  • Next, download the Examples archive and unarchive them.
  • Edit the dist.root property that specifies the JBoss/Tomcat bundle location in the build.xml file, or override the property on the command line to Ant by creating an .ant.properties file in the examples root directory. The default location is /tmp/JBoss-2.4.0_Tomcat-3.2.3, so if you unarchive the JBoss/Tomcat bundle in your /tmp directory, you're ready to go.
  • Build and deploy the example ears by running Ant within the examples directory. Your output should look something like this:
     examples 210>ant
     Buildfile: build.xml
     
     validate:
     
     fail_if_not_valid:
     
     init:
          [echo] Using jboss.dist=/tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss
          [echo] Using classpath=/tmp/JBoss-2.4.0_Tomcat-3.2.3/...
     
     compile:
         [mkdir] Created dir: /home/starksm/examples/build/classes
         [javac] Compiling 7 source files to /home/starksm/examples/build/classes
     
     jar1:
         [mkdir] Created dir: /home/starksm/examples/build/META-INF
          [copy] Copying 1 file to /home/starksm/examples/build/META-INF
          [copy] Copying 1 file to /home/starksm/examples/build/META-INF
           [jar] Building jar: /home/starksm/examples/build/ssbean1.jar
     
     war1:
         [mkdir] Created dir: /home/starksm/examples/build/web/WEB-INF/classes/org/jboss/docs/jaas/howto
          [copy] Copying 1 file to /home/starksm/examples/build/web/WEB-INF
          [copy] Copying 1 file to /home/starksm/examples/build/web/WEB-INF
          [copy] Copying 1 file to /home/starksm/examples/build/web/WEB-INF/classes/org/jboss/docs/jaas/howto
           [jar] Building jar: /home/starksm/examples/build/tutorial1.war
     
     ear1:
          [copy] Copying 1 file to /home/starksm/examples/build/META-INF
           [jar] Building jar: /home/starksm/examples/build/tutorial1.ear
     
     ...
     
     ears:
          [copy] Copying 1 file to /tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/deploy
          [copy] Copying 1 file to /tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/deploy
         [mkdir] Created dir: /tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/conf/jaas_howto
          [copy] Copying 19 files to /tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/conf/jaas_howto
          [copy] Copying 1 file to /tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/conf/jaas_howto
    
    If you see something similar to this:
     examples 212>ant
     Buildfile: build.xml
     
     validate:
     
     fail_if_not_valid:
     
     BUILD FAILED
     
     /home/starksm/examples/build.xml:34: jboss.dist=/usr/local/JBoss-2.4.0_Tomcat-3.2.3/jboss is not a valid JBoss dist directory
    
    you need to correct the dist.root property's value in the Ant build.xml file in the examples directory as indicated in the previous step.
  • Now start the JBoss server with the jaas_howto configuration, which contains the JAAS login configuration file and was created by the Ant build process. To start JBoss, go to the jboss/bin directory and execute the run.sh or run.bat script as appropriate for your operating system, passing in the jaas_howto config name. Here's an example from a Linux system with key output emphasized:
     bin 358>./run.sh jaas_howto
     JBOSS_CLASSPATH=:run.jar:../lib/crimson.jar
     jboss.home = /tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss
     Using JAAS LoginConfig: file:/tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/conf/jaas_howto/auth.conf
     Using configuration "jaas_howto"
     [root] Started Log4jService, config=file:/tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/conf/jaas_howto/log4j.properties
     [Info] Java version: 1.3.1,Sun Microsystems Inc.
     [Info] Java VM: Java HotSpot(TM) Server VM 1.3.1-b24,Sun Microsystems Inc.
     [Info] System: Linux 2.4.3-12,i386
     ...
     [Container factory] Deployed application: file:/tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/tmp/deploy/Default/tutorial1.ear
     [J2EE Deployer Default] Starting module tutorial1.war
     [EmbeddedTomcatSX] deploy, ctxPath=/jaas-example1, warUrl=file:/tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/tmp/deploy/Default/tutorial1.ear/web1003/
     [J2EE Deployer Default] J2EE application: file:/tmp/JBoss-2.4.0_Tomcat-3.2.3/jboss/deploy/tutorial1.ear is deployed.
     
     [Service Control] Started 28 services
     [Default] JBoss 2.4 BETA(Rel_2_4_0_25) Started in 0m:15s
    

The tutorial1.ear contents

tutorial1.ear

consists of a single Webpage, a secured servlet, an unsecured servlet, and two secured stateless session beans. As the servlet and session bean code is trivial, I'll leave its inspection as an exercise for you. The interesting aspect of the example is the deployment descriptors' security-related elements and the configuration of the JAAS login module associated with the security domain.

tutorial1.ear

's contents are shown below with the security-related files

emphasized:

 tutorial1.ear 
 + META-INF
 |-- MANIFEST.MF
 |-- application.xml
 + ssbean1.jar
 |-- + META-INF
 |----- ejb-jar.xml
 |----- jboss.xml
 |-- + org/jboss/docs/jaas/howto
 |----- Session.class
 |----- SessionHome.class
 |----- PrivateSessionBean.class
 |----- PublicSessionBean.class
 |-- roles.properties
 |-- users.properties
 + tutorial1.war
 |-- + WEB-INF
 |----- web.xml
 |----- jboss-web.xml
 |----+ classes/org/jboss/docs/jaas/howto
 |------ EJBServlet.class
 |-- index.html

We'll look at each file in turn.

The tutorial1.ear/ssbean1.jar/META-INF/ejb-jar.xml descriptor

The following

ejb-jar.xml

deployment descriptor declares the tutorial EJBs and their security requirements:

 <?xml version = "1.0" encoding = "UTF-8"?>
 <!DOCTYPE ejb-jar
   PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"
   "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
 
 <ejb-jar>
   <display-name>SecurityTests</display-name>
   <enterprise-beans>
     <session>
       <description>A trivial stateless session echo bean</description>
       <ejb-name>PublicSession</ejb-name>
       <home>org.jboss.docs.jaas.howto.SessionHome</home>
       <remote>org.jboss.docs.jaas.howto.Session</remote>
       <ejb-class>org.jboss.docs.jaas.howto.PublicSessionBean</ejb-class>
       <session-type>Stateless</session-type>
       <transaction-type>Container</transaction-type>
       <ejb-ref>
         <ejb-ref-name>ejb/PrivateSession</ejb-ref-name>
         <ejb-ref-type>Session</ejb-ref-type>
         <home>org.jboss.docs.jaas.howto.SessionHome</home>
         <remote>org.jboss.docs.jaas.howto.Session</remote>
         <ejb-link>PrivateSession</ejb-link>
       </ejb-ref>
       <security-role-ref>
         <role-name>EchoUser</role-name>
         <role-link>Echo</role-link>
       </security-role-ref>
       <security-identity>
         <run-as>
           <role-name>InternalUser</role-name>
         </run-as>
       </security-identity>
     </session>
 
     <session>
       <description>A trivial stateful session echo bean</description>
       <ejb-name>PrivateSession</ejb-name>
       <home>org.jboss.docs.jaas.howto.SessionHome</home>
       <remote>org.jboss.docs.jaas.howto.Session</remote>
       <ejb-class>org.jboss.docs.jaas.howto.PrivateSessionBean</ejb-class>
       <session-type>Stateful</session-type>
       <transaction-type>Container</transaction-type>
       <security-role-ref>
         <role-name>InternalUser</role-name>
         <role-link>InternalUser</role-link>
       </security-role-ref>
     </session>
   </enterprise-beans>
 
   <assembly-descriptor>
     <security-role>
       <role-name>Coder</role-name>
     </security-role>
     <security-role>
       <role-name>Echo</role-name>
     </security-role>
     <security-role>
       <role-name>InternalUser</role-name>
     </security-role>
 
     <method-permission>
       <role-name>Echo</role-name>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>*</method-name>
       </method>
     </method-permission>
     <method-permission>
       <role-name>InternalUser</role-name>
       <method>
         <ejb-name>PrivateSession</ejb-name>
         <method-name>*</method-name>
       </method>
     </method-permission>
     <method-permission>
       <role-name>Coder</role-name>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>create</method-name>
       </method>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>remove</method-name>
       </method>
     </method-permission>
     <method-permission>
       <unchecked/>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>noop</method-name>
       </method>
     </method-permission>
 
     <exclude-list>
       <description>Methods that cannot be used in this
         deployment</description>
       <method>
         <ejb-name>PublicSession</ejb-name>
         <method-name>restricted</method-name>
       </method>
     </exclude-list>
   </assembly-descriptor>
 </ejb-jar>

Items of note include:

  • The security-role-ref element is where the PublicSession bean declares an EchoUser role name, which links to the Echo role name. That element indicates that the PublicSession bean queries the EJBContext.isCallerInRole with a EchoUser role name string.
  • The security-identity/run-as/role-name InternalUser declaration indicates that a principal with a role named InternalUser handles any EJB calls made by the PublicSession bean.
  • The PrivateSession bean declares an InternalUser role name to indicate that the bean queries the EJBContext.isCallerInRole with an InternalUser role name string.
  • In the assembly-descriptor section, logical roles of Echo, Coder, and InternalUser are declared using security-role elements.
  • The Echo role receives permission to access any method in the PublicSession bean in the first method-permission element.
  • The InternalUser role receives permission to access any PrivateSession bean method in the second method-permission element.
  • The fourth method-permission element indicates that any authenticated user can call the PublicSession noop method with the unchecked element instead of a role-name element.
  • The exclude-list element declares that no one can call the PublicSession restricted method.

The tutorial1.ear/ssbean1.jar/META-INF/jboss.xml descriptor

The code below is the JBoss-server-specific EJB deployment descriptor:

 <?xml version="1.0" encoding="UTF-8"?>
 
 <jboss>
   <!-- All bean containers use this security manager by default -->
   <security-domain>java:/jaas/example1</security-domain>
 
   <enterprise-beans>
     <session>
       <ejb-name>PublicSession</ejb-name>
       <jndi-name>example1/PublicSession</jndi-name>
     </session>
 
     <session>
       <ejb-name>PrivateSession</ejb-name>
       <jndi-name>example1/PrivateSession</jndi-name>
     </session>
   </enterprise-beans>
 
 </jboss>

This descriptor indicates that the security manager located at the JNDI name

java:/jaas/example1

secures all EJBs in the

ssbean1.jar

. The JNDI name's final component (

example1

) determines which login modules are associated with the security domain.

The tutorial1.ear/ssbean1.jar/roles.properties, users.properties files

The

org.jboss.security.auth.spi.UsersRolesLoginModule

custom JAAS login module shipped with the JBossSX framework uses the

roles.properties

and

users.properties

files. This simple login module uses Java properties format files for mapping usernames to passwords and usernames to roles names. The

roles.properties

file maps a username to one or more role names using the format

username[.RoleGroup]=role1_name[,role2_name,...]

. A

RoleGroup

names the

Group

that will be created to hold given roles. With no specified

RoleGroup

, it defaults to

Roles

. Here's the

roles.properties

file for the example:

 # roles.properties
 java=Echo
 duke=Java,Coder
 java.CallerPrincipal=caller_java
 duke.CallerPrincipal=caller_duke

The code above maps the username

java

to the role name

Echo

under the

Roles RoleGroup

, and the role name

caller_java

under the

CallerPrincipal RoleGroup

. The

users.properties

file maps a username to a user password. Here's the

users.properties

file for the example:

 # users.properties
 java=echoman
 duke=javaman

The above code maps the username

java

to the password

echoman

.

The tutorial1.ear/tutorial1.war/WEB-INF/web.xml descriptor

The following

web.xml

deployment descriptor declares the tutorial servlets and their EJB references and security requirements:

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE web-app
   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
   "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
 
 <web-app>
 <!-- ### Servlets -->
   <servlet>
     <servlet-name>SecureServlet</servlet-name>
     <servlet-class>org.jboss.docs.jaas.howto.EJBServlet</servlet-class>
   </servlet>
   <servlet>
     <servlet-name>UnsecureServlet</servlet-name>
     <servlet-class>org.jboss.docs.jaas.howto.EJBServlet</servlet-class>
   </servlet>
   <servlet-mapping>
     <servlet-name>SecureServlet</servlet-name>
     <url-pattern>/restricted/SecureServlet</url-pattern>
   </servlet-mapping>
   <servlet-mapping>
     <servlet-name>UnsecureServlet</servlet-name>
     <url-pattern>/UnsecureServlet</url-pattern>
   </servlet-mapping>
 
 <!-- ### Security -->
   <security-constraint>
     <web-resource-collection>
       <web-resource-name>Restricted</web-resource-name>
       <description>Declarative security tests</description>
       <url-pattern>/restricted/*</url-pattern>
       <http-method>HEAD</http-method>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
       <http-method>PUT</http-method>
       <http-method>DELETE</http-method>
     </web-resource-collection>
     <auth-constraint>
       <role-name>Echo</role-name>
     </auth-constraint>
     <user-data-constraint>
       <description>no description</description>
       <transport-guarantee>NONE</transport-guarantee>
     </user-data-constraint>
   </security-constraint>
 
   <login-config>
     <auth-method>BASIC</auth-method>
     <realm-name>JAAS Tutorial Servlets</realm-name>
   </login-config>
 
   <security-role>
     <description>A user allowed to invoke echo methods</description>
     <role-name>Echo</role-name>
   </security-role>
 
 <!-- ### EJB References (java:comp/env/ejb) -->
   <ejb-ref>
     <ejb-ref-name>ejb/SecuredEJB</ejb-ref-name>
     <ejb-ref-type>Session</ejb-ref-type>
     <home>org.jboss.docs.jaas.howto.SessionHome</home>
     <remote>org.jboss.docs.jaas.howto.Session</remote>
   </ejb-ref>
   <ejb-ref>
     <ejb-ref-name>ejb/PrivateEJB</ejb-ref-name>
     <ejb-ref-type>Session</ejb-ref-type>
     <home>org.jboss.docs.jaas.howto.SessionHome</home>
     <remote>org.jboss.docs.jaas.howto.Session</remote>
   </ejb-ref>
 
 </web-app> 

The security items of note include:

  • The security-constraint/web-resource-collection/url-pattern element declaration indicates that all content under /restricted must be secured for the indicated HTTP methods.
  • The auth-constraint/role-name element indicates that only users with an Echo role can access the content under /restricted.
  • The login-config/auth-method element with the BASIC value indicates that BASIC HTTP authorization will authenticate users attempting to access content under /restricted.
  • The SecureServlet requires authenticated access, since its servlet-mapping/url-pattern element is under the /restricted path.

The tutorial1.ear/tutorial1.war/WEB-INF/jboss-web.xml descriptor

Here is the JBoss-server-specific Web application deployment descriptor:

 <?xml version="1.0" encoding="UTF-8"?>
 
 <jboss-web>
   <security-domain>java:/jaas/example1</security-domain>
 
   <ejb-ref>
     <ejb-ref-name>ejb/SecuredEJB</ejb-ref-name>
     <jndi-name>example1/PublicSession</jndi-name>
   </ejb-ref>
   <ejb-ref>
     <ejb-ref-name>ejb/PrivateEJB</ejb-ref-name>
     <jndi-name>example1/PrivateSession</jndi-name>
   </ejb-ref>
 </jboss-web> 

This code indicates that the security manager located at the JNDI name

java:/jaas/example1

handles security for all secured Web content. Since the

security-domain

element matches that used by the

jboss.xml

deployment descriptor, the same security manager secures the ear EJBs and Web content.

The jboss/conf/jaas_howto/auth.conf login configuration file

The final security-related file is outside of

tutorial1.ear

. The

auth.conf

file in the JBoss server configuration directory is the JAAS login configuration file. That file consists of named login configuration entries. Here's the version of

auth.conf

that we will use for the tutorial:

 example1 {
 // A properties file LoginModule that supports CallerPrincipal mapping
     org.jboss.security.auth.spi.UsersRolesLoginModule required
     unauthenticatedIdentity=nobody
     ;
 };
 
 example2 {
 /* A JDBC-based LoginModule
 LoginModule options:
 dsJndiName: The name of the DataSource of the database containing the Principals, Roles tables
 principalsQuery: The prepared statement query equivalent to:
     "select Password from Principals where PrincipalID=?"
 rolesQuery: The prepared statement query equivalent to:
     "select Role, RoleGroup from Roles where PrincipalID=?"
 */
     org.jboss.security.auth.spi.DatabaseServerLoginModule required
     dsJndiName="java:/DefaultDS"
     principalsQuery="select Password from Principals where PrincipalID=?"
     rolesQuery="select Role, RoleGroup from Roles where PrincipalID=?"
     unauthenticatedIdentity=nobody
     ;
 };

In the example file above, there are two entries,

example1

and

example2

. The

example1

entry contains a single login module whose class name is

org.jboss.security.auth.spi.UsersRolesLoginModule

, which is required for successful authentication. The login module is passed a single option named

unauthenticatedIdentity

with a value of

nobody

. The

unauthenticatedIdentity

option authenticates anonymous users as the identity

nobody

. The

unauthenticatedIdentity

typically provides an identity to unsecured Web content that calls EJBs that use the

EJBContext.getCallerPrincipal()

method. Since this method cannot return a null value as per the EJB specification, the application server must provide a mechanism to map the nonexistent user onto an identity. JBoss pushes this responsibility onto the security domain login modules.

Test the tutorial1.ear deployment

The example's final step is to test that the security constraints do in fact work. If your browser runs on the same host as the JBoss server, launch your Web browser and open the Web application

index.html

file located at

http://localhost:8080/jaas-example1/index.html

. Figure 7 shows what the Webpage should look like. Each link is a test case, with the expected result shown as

[PASS]

or

[FAIL]

based on the specified security. We'll walk through links 1 and 4 to verify both a passing and failing test.

Figure 7. The tutorial1.ear index.html page Click on thumbnail to view full-size image.

The first link corresponds to the call sequence illustrated in Figure 8. Each access or method invocation lists the role that the caller must possess for the access to be granted.

Figure 8. The tutorial1.ear index.html page Click on thumbnail to view full-size image.

Traverse Link 1 to invoke the

SecureServlet

and enter

java

for the username and

echoman

as the password in the login dialog. Figure 9 shows the browser result.

Figure 9. The expected browser result Click on thumbnail to view full-size image.

The JBoss server console should display the following output:

 [Default] User 'nobody' authenticated.
 [Default] User 'java' authenticated.
 [Default] PublicSessionBean.ejbCreate() called
 [Default] PublicSessionBean.echo, arg=Hello
 [Default] PublicSessionBean.echo, callerPrincipal=caller_java
 [Default] PublicSessionBean.echo, isCallerInRole('EchoUser')=true
 [Default] PrivateSessionBean.ejbCreate() called
 [Default] PublicSessionBean.echo, created PrivateSession
 [Default] PrivateSessionBean.echo, arg=Hello
 [Default] PrivateSessionBean.echo, callerPrincipal=caller_java
 [Default] PrivateSessionBean.echo, isCallerInRole('InternalUser')=false

The first

User 'nobody' authenticated.

line results because Tomcat tried to determine whether the servlet request had a remote user associated with it. That caused a query to the security interceptor with a null username and password, because the browser was not asked to provide any login information. The null username and password is authenticated as the anonymous user

nobody

we configured in the server

auth.conf

file. The following

User 'java' authenticated.

line is the result of the

HTTP BASIC

authorization login. Note that

PrivateSessionBean.echo()

method's check to see if the caller has the

InternalUser

role returns false. This seemingly incorrect result is in accord with our interpretation of the

EJB 2.0 PFD2 spec

, which states on page 439:

Note that isCallerInRole(String roleName) tests the principal that represents the caller of the enterprise bean, not the principal that corresponds to the run-as security identity for the bean, if any.

Now follow Link 4 to try to access the

PrivateSession

bean's echo method from the

SecureServlet

. That should fail, since only the

PublicSession

has been configured to run as the

InternalUser

role. You should see a 500 error and a root cause exception with the message:

 Insufficient method permissions, principal=java, method=create, requiredRoles=[InternalUser];

This message verifies that the

method-permission

element, which required a

InternalUser

role, is enforced. I encourage you to try the additional test cases in the

tutorial1.ear

example and investigate the second example,

tutorial2.ear

, which was created and deployed during the examples build. The

tutorial2.ear

example differs from

tutorial1.ear

only in its choice of security domain name and thus login module configuration.

tutorial2.ear

uses a Java Database Connectivity-based login module to demonstrate how to access security information from a database.

Secure your J2EE apps

Although the mapping of application security roles onto a application server deployment environment is currently a nonportable, application-server-specific task, this article has demonstrated how the standard JAAS security could be used to implement the J2EE declarative security model using a simple

Subject

-based usage pattern. Hopefully, future versions of J2EE will extend the portability of security closer to the deployment layer using similar techniques based on JAAS or equivalent standards-based APIs. This article has peered into the JBossSX security manager implementation of the J2EE declarative security model used by the JBoss EJB and Web containers. It focused on how the JAAS login modules configured for a security domain provide the security information via a

Subject

usage pattern. I also covered the steps required to create a custom login module for security infrastructures not supported by bundled JBossSX login modules. With this information, you can secure your enterprise applications either by configuring the login modules bundled with JBoss or by writing your own. :END_BODY

Scott Stark currently serves as the lead developer for the JBossSX security extension framework and one of the lead developers for the JBoss kernel and the JBoss release manager; he is also a partner in the JBoss Group. He has a Ph.D. in chemical engineering; that field was where he started working with parallel/distributed computing on machines like the BBN TC2000 and CM2. Scott has been doing distributed computing for more than 10 years, using a number of languages and paradigms. His Java experience includes more than four years of work in the technologies that make up the J2EE platform. :END_BIO

Learn more about this topic

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