ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Java and Security, Part 2
Pages: 1, 2, 3, 4, 5, 6, 7, 8

A Sample JAAS Client

Let's look at how to build a JAAS client that can authenticate itself to WebLogic. We'll cover this example using a top-down approach, starting with what the JAAS client needs to accomplish and then breaking it down into the individual components of its implementation. Let's begin with the main class, SimpleJAASClient, which takes the following steps:

  1. It reads the username, password, and URL as input arguments from the command line.
  2. It attempts to connect to the specified URL and then authenticates the client using the supplied username and password.
  3. It executes a privileged action under the newly acquired authenticated subject. Example 17-1 lists the source code for our JAAS client.

Example 17-1 lists the source code for our JAAS client.

Example 17-1. A sample JAAS client

package com.oreilly.wlguide.security.jaas;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
public class SimpleJAASClient {
   public static void main(String[] args) {
      String username = args[0];
      String password = args[1];
      String url = args[2];
      LoginContext loginContext = null;
      // Create a LoginContext using our own CallBackHander
      try {
         loginContext = new LoginContext("Simple",
            new SimpleCallbackHandler(username, password, url));
      } catch (Exception e) {
         // Can get a SecurityException or a LoginException
         e.printStackTrace( );
         System.exit(-1);
      }
      // Now authenticate. If we don't get an exception, we succeeded
      try {
         loginContext.login( );
      } catch (Exception e) {
         // Can get FailedLoginException, AccountExpiredException,
         // or CredentialExpiredException
         e.printStackTrace( );
         System.exit(-1);
      }
      // Retrieve authenticated subject and perform action using it
      Subject subject = loginContext.getSubject( );
      SimpleAction simpleAction = new SimpleAction(url);
      weblogic.security.Security.runAs(subject, simpleAction);
   }
}

Notice how we've highlighted the important bits of the JAAS client. Our first critical step is to establish a LoginContext object:

loginContext = new LoginContext("Simple",
   new SimpleCallbackHandler(username, password, url));

The LoginContext object initializes the client with the CallBackHandler and LoginModule instances that will be used during JAAS authentication. The second argument to the constructor is our own CallBackHandler instance that will be used by the LoginModule to retrieve the user's credentials, and the URL of the WebLogic instance that will authenticate our client.

The first argument to the constructor, Simple, is used to look up the appropriate LoginModule for the client. JAAS clients rely on a configuration file that maps the names of JAAS login modules to their implementation, and also may specify additional parameters. Example 17-2 lists the JAAS configuration file that we used.

Example 17-2. Login configuration file, jaas.config

Simple {
   weblogic.security.auth.login.UsernamePasswordLoginModule
   required
};

Our configuration file contains a single entry for Simple that specifies WebLogic's LoginModule for authentication on the basis of the given username and password: weblogic.security.auth.login.UsernamePasswordLoginModule. When you run the JAAS client, you must specify the location of this configuration file using a system property. Here's how you would run our sample JAAS client:

java -Djava.security.auth.login.config=jaas.config \
   com.oreilly.wlguide.security.jaas.SimpleJAASClient system pssst t3://10.0.10.10:
8001/

In this way, we can configure the LoginContext to use WebLogic's LoginModule, which supports authentication using a username-password combination. Later, we'll see how you can use the JAAS configuration file to transparently substitute this with your own LoginModule implementation.

After establishing the login context, we've invoked the loginContext.login( ) method to execute the actual login. Our LoginContext will utilize the configured login module and callback handler objects and attempt to authenticate the client with the server. If this client is authenticated successfully, you can retrieve the authenticated subject from the LoginContext:

Subject subject = loginContext.getSubject( );

The getPrincipals( ) method on this authenticated Subject retrieves all of the principals associated with the user. For instance, if our JAAS client authenticated using the credentials of the system administrator, the authenticated Subject holds two principals: system, which represents the user, and Administrators, which represents the user's group. Now, we can use this subject to execute one or more "privileged" actions. In other words, these actions are performed within the context of this authenticated subject:

weblogic.security.Security.runAs(subject, simpleAction);

There is one caveat here—the client must invoke the runAs( ) method on WebLogic's Security class. The runAs( ) method accepts two parameters: the authenticated Subject, and a PrivilegedAction object, which wraps the applicationspecific interaction with the server. Example 17-3 illustrates the action that our JAAS client wishes to execute.

Example 17-3. A very simple action

package com.oreilly.wlguide.security.jaas;
import java.security.PrivilegedAction;
import java.sql.Connection;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class SimpleAction implements PrivilegedAction {
   private static final String JNDI_NAME = "jdbc.xpetstore";
   private String url;
   public SimpleAction(String url) {
      this.url = url;
   }
   public Object run( ) {
      Object obj = null;
      try {
         Context ctx = null;
         Hashtable ht = new Hashtable( );
         ht.put(Context.INITIAL_CONTEXT_FACTORY,
         "weblogic.jndi.WLInitialContextFactory");
         ht.put(Context.PROVIDER_URL, url);
         // Get a context for the JNDI lookup
         ctx = new InitialContext(ht);
         // do any work here
         DataSource ds =(javax.sql.DataSource) ctx.lookup(JNDI_NAME);
         // ...
      } catch (Exception e) {
         e.printStackTrace( );
      }
      return obj;
   }
}

Here you need to recognize the following significant points:

  • The class implements the java.security.PrivilegedAction interface. Any JAAS client can then invoke an instance of this class within the context of the authenticated Subject.

  • The run( ) method encapsulates the client's interaction with the server. Typically, the client will establish a JNDI context, use it to grab resources bound to the JNDI tree, and then invoke/access these resources. In the earlier example, we used the JNDI context to acquire a JDBC data source.

  • When we establish the JNDI context within the PrivilegedAction.run( ) method, we don't provide any user credentials for JNDI authentication. The authenticated Subject supplied by the JAAS client to the runAs( ) method ensures that the PrivilegedAction object is invoked within the context of this subject. That is, the runAs( ) method is responsible for associating the authenticated Subject with the current thread.

Example 17-4 lists the source code for our CallBackHandler class. In general, the callback handler would interact with the client in some way that prompts the user for the username and password to be used for authentication. In the case of our simple JAAS client, we supply the necessary credentials and URL to the constructor of our callback handler so that the callbacks can easily return this information.

Example 17-4. A simple callback handler

package com.oreilly.wlguide.security.jaas;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import weblogic.security.auth.callback.URLCallback;

public class SimpleCallbackHandler implements CallbackHandler {
   private String username = null;
   private String password = null;
   private String url = null;
   
   public SimpleCallbackHandler(String pUsername, String pPassword, String pUrl) {
      username = pUsername; password = pPassword; url = pUrl;
   }

   public void handle(Callback[] callbacks)
         throws java.io.IOException, UnsupportedCallbackException {
      for (int i = 0; i < callbacks.length; i++) {
         if (callbacks[i] instanceof NameCallback) {
            NameCallback nc = (NameCallback) callbacks[i];
            nc.setName(username);
         } else if (callbacks[i] instanceof URLCallback) {
            URLCallback uc = (URLCallback) callbacks[i];
            uc.setURL(url);
         } else if (callbacks[i] instanceof PasswordCallback) {
            PasswordCallback pc = (PasswordCallback) callbacks[i];
            pc.setPassword(password.toCharArray( ));
         } else {
            throw new UnsupportedCallbackException(
            callbacks[i], "Unrecognized Callback");
         }
      }
   }
}

The final piece of the puzzle is the JAAS login module. Earlier, we saw how the JAAS configuration file enabled us to set up our client to use WebLogic's login module for username-password authentication, the UsernamePasswordLoginModule. This Login-Module class expects our callback handler to deal with username and password callbacks, and optionally, the URL callback as well. The login( ) method provides the entry point for the JAAS framework into the LoginModule. It uses the user's credentials to authenticate the client with WebLogic and, if successful, returns an authenticated Subject populated with the appropriate principals.

We could have easily constructed our own login module and modified the configuration file to reference this module. The login( ) method is the most important within the LoginModule implementation class because this method is responsible for performing the actual authentication. Typically, it must use the configured callback handler to retrieve the username, password, and URL. It must then create an Environment object populated with this data, and invoke the authenticate( ) method on WebLogic's Authenticate class to execute the login and generate an authenticated Subject populated with required principals. The following code shows how to accomplish this authentication:

weblogic.jndi.Environment env = new weblogic.jndi.Environment( );
env.setProviderUrl(url);
env.setSecurityPrincipal(username);
env.setSecurityCredentials(password);
weblogic.security.auth.Authenticate.authenticate(env, subject);

In general, WebLogic's login module should be sufficient for most purposes; it is unlikely that you will need to provide your own LoginModule implementation.

Pages: 1, 2, 3, 4, 5, 6, 7, 8

Next Pagearrow