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:
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.
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.
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.
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-roles 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.
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.
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.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>
security-role-ref and security-role elements are equivalent to their EJB counterparts.
Figure 3. The JBoss-specific EJB and Web deployment descriptor security elements
Click on thumbnail to view full-size image.
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.
LoginContext object, which passes the name of a Configuration entry to determine which LoginModule will be created. The LoginModules define the authentication technology. LoginModules often use a username and password to verify identity, but any form of identity and identity verification will work.
Subject (javax.security.auth.Subject)
Principal (java.security.Principal)
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 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 Principals. A Subject may have many Principals. 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 Subjects. To retrieve the Principals associated with a Subject, two methods are available:
public Set getPrincipals();
public Set getPrincipals(Class c);
Principals contained in the Subject. The second method only returns those Principals that are instances of Class c or Class c's subclasses. An empty set will return if the Subject has no associated Principals. Note that the java.security.acl.Group interface is a subinterface of java.security.Principal, and so an instance in the Principals set may represent a logical grouping of other principals or groups of principals.
Subject:
LoginContext.
LoginContext consults a Configuration to load all the LoginModules configured for that application.
LoginContext's login method.
LoginModules. Each LoginModule attempts to authenticate the Subject. Upon success, LoginModules associate relevant Principals and credentials with the Subject.
LoginContext returns the authentication status to the application.
Subject from the LoginContext.
LoginContext class provides the basic methods for authenticating Subjects 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 LoginModules -- configured for a particular application. Therefore, you can plug in different LoginModules 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;
login() invokes, all the configured LoginModules' respective login methods invoke to perform authentication. If authentication succeeds, the following method can retrieve
the authenticated Subject (which may now hold Principals, public credentials, and private credentials):
public Subject getSubject();
Subject and remove its authenticated Principals and credentials, use the method below:
public void logout() throws LoginException;
Subject using the LoginModules 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();
}
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);
login() method starts authentication; its signature is:
boolean login() throws LoginException;
LoginModule is considered Phase 1 of JAAS authentication. Authentication completes when the LoginContext calls:
boolean commit() throws LoginException;
commit() method continues with Phase 2: associating relevant Principals, 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;
Subject:
boolean logout() throws LoginException;
Principals 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. LoginModules 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 LoginModules 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;
Callback interface. It is a tagging interface that features several implementations, including NameCallback and PasswordCallback. LoginModules use a Callback to request information required by the authentication mechanism the LoginModule encapsulates. LoginModules pass an array of Callbacks directly to the CallbackHandler's handle method during the authentication's login phase.
Figure 5. Security component interaction overview
Click on thumbnail to view full-size image.
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.
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.
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.
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.
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:
java.security.Principal that corresponds to the client identity.
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.
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.
ejb-jar.xml descriptor role-name elements of all method-permission elements containing the invoked method.
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.
SecurityException. If no SecurityException is thrown, access to the EJB method is allowed; the SecurityInterceptor passes the method invocation to the next container interceptor.
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.
Subject. If the LoginModules 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.
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)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 Principals set, but in named role sets using java.security.acl.Group instances. The Group interface -- a collection of Principals and/or Groups -- 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.
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;
}
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;
}
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.
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.
JBoss-2.4.0.26_Tomcat-3.2.3.zip; it unarchives to create a JBoss-2.4.0_Tomcat-3.2.3 directory.
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.
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
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
dist.root property's value in the Ant build.xml file in the examples directory as indicated in the previous step.
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
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
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>
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.
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.
PrivateSession bean declares an InternalUser role name to indicate that the bean queries the EJBContext.isCallerInRole with an InternalUser role name string.
assembly-descriptor section, logical roles of Echo, Coder, and InternalUser are declared using security-role elements.
Echo role receives permission to access any method in the PublicSession bean in the first method-permission element.
InternalUser role receives permission to access any PrivateSession bean method in the second method-permission element.
method-permission element indicates that any authenticated user can call the PublicSession noop method with the unchecked element instead of a role-name element.
exclude-list element declares that no one can call the PublicSession restricted method.
<?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>
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 filesorg.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
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
java to the password echoman. The tutorial1.ear/tutorial1.war/WEB-INF/web.xml descriptorweb.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>
security-constraint/web-resource-collection/url-pattern element declaration indicates that all content under /restricted must be secured for the indicated HTTP methods.
auth-constraint/role-name element indicates that only users with an Echo role can access the content under /restricted.
login-config/auth-method element with the BASIC value indicates that BASIC HTTP authorization will authenticate users attempting to access content under /restricted.
SecureServlet requires authenticated access, since its servlet-mapping/url-pattern element is under the /restricted path.
<?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>
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 filetutorial1.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
;
};
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 deploymentindex.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.
Figure 8. The tutorial1.ear index.html page
Click on thumbnail to view full-size image.
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.
[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
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];
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.
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