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:
- It reads the username, password, and URL as input arguments from the command line.
- It attempts to connect to the specified URL and then authenticates the client using the supplied username and password.
- 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.PrivilegedActioninterface. Any JAAS client can then invoke an instance of this class within the context of the authenticatedSubject.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 authenticatedSubjectsupplied by the JAAS client to therunAs( )method ensures that thePrivilegedActionobject is invoked within the context of this subject. That is, therunAs( )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.