JBoss comes out-of-the-box with a handful of great login modules, but lets face it, you need a custom one to integrate with your existing security infrastructure. This overview shows you how to secure your EJB application with a custom server login module, and also to use a custom client login module to authenticate your EJB client applications.
JBoss makes use of the Java Authentication and Authorization Service API to authenticate and authorize users in your EJB applications. If you are not already familiar with creating JAAS login modules, I would recommend that you read my entry on Creating JAAS Login Modules to get an understanding of how they work.
This overview will show you how to secure your EJB Applications in JBoss using JAAS, and how to authenticate your client applications to the JBoss server. I will assume that since you are at the level of creating custom login modules, that you already know how to set up your XML Descriptor file for role based authorization, and have already done so. If you haven’t, you should… otherwise, it makes it difficult to test your login module! We won’t go over the steps of creating an EJB, but for this example we’ll assume that we have a stateless session bean called HelloBean. The HelloBean’s remote interface defines one method, hello(), that returns the String “Hello” to the client. HelloBean’s XML Descriptor is set up such that the caller must be in the role “ExampleRole”.
So here’s the scenario, you have an existing security infrastructure that provides username and password based authentication. You want to perform client side authentication, and have the server lookup what roles the user is in from some database or other part of the existing security infrastructure. My entry on Creating JAAS Login Modules will show you how to set up the client side login module that performs client side authentication. Everything in that entry holds true for this tutorial, with only a couple additional steps needed for JBoss. The first thing that is required is that in addition to your own custom login module, you also use JBoss’s ClientLoginModule. To do this, your security domain configuration file should look like this:
exampleDomain { net.eskeeter.jaasexample.CustomLoginModule required; org.jboss.security.ClientLoginModule required; };
JBoss’s ClientLoginModule will get the username and password from the callback handler you initialized the login context with. If you look carefully at the implementation of the CustomCallbackHandler (in the jaas login module entry), you’ll see that it only interacts with the user the first time it is called. Since each login module in the context shares the same instance of the callback handler, the user is not prompted for their credentials twice. There are other (and probably “better”) ways to accomplish this using the shared state map, but this way is effective as well.
So now that your client is authenticated, you need to get the principal and credentials to the server. Note that if you do not need to perform further password authentication on the server side, there is no reason to pass the actual password to the server, although for JBoss, you do need to pass something to the server. In the implementation I wrote for jETIA, I pass the hard-coded string “IDontNeedThePassword” to the server as the credentials. So how do you get this information to the server? It’s easy. JBoss’s ClientLoginModule takes the username and password and puts them in the org.jboss.security.SecurityAssociation class. You get the information to the server by initializing your JNDI Naming Context with them as follows:
HashTable props = new HashTable(); props.put( Context.SECURITY_PRINCIPAL, SecurityAssociation.getPrincipal() ); props.put( Context.SECURITY_CREDENTIALS, SecurityAssociation.getCredential() ); InitialContext initialContext = new InitialContext( props );
From then on, when you use that naming context to lookup EJB Home and Remote references, that information can be obtained by the server in the caller’s context. Using a Service Locator pattern (Core J2EE Patterns, Prentice Hall, Alur, Crupi and Malks) simplifies this, since you only have one initial context in the whole application.
So that’s all there is to the client side additions. By requiring JBoss’s ClientLoginModule and initializing the JNDI naming context with the principal and credentials, you have fulfilled all your client side obligations for using custom JBoss login modules. Implementing the server side login module is no more difficult than it was to do the client side.
To write the server side login module, all you need to do is implement the JAAS module, set up your EJB Application in a security domain, and tell JBoss to use your module for that domain. Recall that our client’s CustomLoginModule subclasses the JAAS javax.security.auth.login.LoginModule class. The server side login module should subclass org.jboss.security.auth.spi.AbstractLoginModule, which is a subclass of javax.security.auth.login.LoginModule. The reason we subclass JBoss’s class instead of directly extending the JAAS class is because JBoss expects to find the user and role principals arranged in a specific manner. Using the AbstractLoginModule ensures that these are set up properly for JBoss. Our CustomServerLoginModule looks like this:
package net.eskeeter.jbossjaasexample; import java.security.acl.Group; import javax.security.auth.login.LoginException import org.jboss.security.auth.api.AbstractLoginModule; public class CustomServerLoginModule extends AbstractServerLoginModule { private Principal identity; public Principal getIdentity() { return identity; } public Group[] getRoleSets() { ... } public boolean login() throws LoginException { ... } }
The getIdentity() and getRoleSets() methods are abstract in the AbstractServerLoginModule, and must be implemented by our server login module. The login() method has a default implementation, but you’ll typically want to override this do anything you need to do on the server side. This login method is the very same as the one in the JAAS login module, and thus follows the same rules: return true if successful, false if this module should be ignored, or throw a LoginException if an error occurs.
public boolean login() throws LoginException { identity = org.jboss.security.SecurityAssociation.getPrincipal(); if ( identity == null ) { throw new LoginException( "The principal was not found in " + "the SecurityAssociation." ); } loginOk = true; return true; }
Our simple sets the identity principal, and returns true. The boolean loginOk is a protected member of the AbstractServerLoginModule class, and is checked by the commit() method to determine if the login() method succeeded. The getIdentity() method simply returns the principal that should be used as the identity of the subject on the server. The getRoleSets method returns an array of Groups of Principals, our sample implementation is as follows:
public Group[] getRoleSets() { // Your implementation should lookup what roles // the user, identified by the identity principal, is in // from a database, or some group management // system you have in place. Group rolesGroup = new SimpleGroup( "Roles" ); rolesGroup.addMember( new SimplePrincipal( "ExampleRole" ) ); return new Group[]{ rolesGroup }; }
JBoss expects to find the user’s roles in a group called “Roles”. If you have a need for them, you can add other groups as well. Our simple implementation is not very dynamic, it always returns the Roles group with a single role principal, “ExampleRole”.
That’s all there is to writing the custom login module for the server. All that’s left to do now is tell the JBoss what security domain your EJB Application should run in, and to use your server login module for that domain. Setting your EJB Application’s security domain is as easy as adding a tag to your jboss.xml descriptor file:
<jboss> <security-domain>ExampleDomain</security-domain> </jboss>
Note that this security domain does not have anything to do with the security domain you set up on the client. When you build your EJB jar file, put the jboss.xml file along side your ejb-jar.xml file in the jar files META-INF directory. JBoss will process this file with the ejb-jar.xml descriptor file, appending any information found in it to the information found in the ejb-jar.xml file. To tell JBoss to use your login module, you need to edit the /server/