We already have seen many examples of how a Java client authenticates itself to WebLogic Server. In most cases, the client submits a username-password combination as its credentials when setting up the JNDI context:
Hashtable env = new Hashtable( ); env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); env.put(Context.PROVIDER_URL, "t3://10.0.10.10:7001"); env.put(Context.SECURITY_PRINCIPAL, "system"); env.put(Context.SECURITY_CREDENTIALS, "12341234"); Context ctx = new InitialContext(env); // use the JNDI context as "system" user ...
WebLogic also lets you build Java clients that can use the more standard approach to authentication using JAAS. Even though JAAS authentication is somewhat more long-winded than traditional JNDI-based authentication, your clients will be more portable. Because of the pluggable nature of the JAAS framework, it should enable you to benefit from future changes to the authentication technology without changes to the client code.
17.6.1 Anatomy of a JAAS Client
A JAAS client involves the interplay among a number of classes and interfaces, as shown in Figure 17-5. Let's examine how these different objects interact during JAAS-style authentication:
Subject
This represents the goal of the authentication sequence. Once a client has been authenticated, it obtains a Subject instance that is populated with all of the principals that map to the client.
LoginContext
This is responsible for populating the Subject with its principals. Its all-important login( ) method delivers an authenticated Subject back to the client. To construct a LoginContext instance, you need to supply objects of two subsidiary classes: a CallBackHandler and LoginModule instance.
CallBackHandler
This is responsible for retrieving the username and password of the client being authenticated. In the case of a Swing-based application, the CallBackHandler instance could conceivably pop up a dialog box requesting the data from the end user. In fact, the CallBackHandler instance is invoked by the LoginModule.
Figure 17-5. Typical interaction when authenticating a JAAS client
LoginModule
This is any entity capable of authenticating the user's credentials. A separate JAAS configuration file settles how the LoginModule is implemented. In general, you can implement your own login modules. However, WebLogic also provides you with a convenient LoginModule that can authenticate the client via the supplied username and password against a WebLogic instance whose URL is also supplied. On successful authentication, the LoginModule populates the subject with its principals. Using this authenticated subject, the JAAS client can now perform one or more privileged actions.
PrivilegedAction
Any class that implements the PrivilegedAction interface encapsulates the code that a Java client can run within the security context defined by the populated Subject. The weblogic.security.Security.runAs( ) method allows the client to associate a Subject with the current thread, before invoking the privileged action under this security context.
17.6.2 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:
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 application-specific 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:
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 LoginModule 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.
Introduction
Web Applications
Managing the Web Server
Using JNDI and RMI
JDBC
Transactions
J2EE Connectors
JMS
JavaMail
Using EJBs
Using CMP and EJB QL
Packaging and Deployment
Managing Domains
Clustering
Performance, Monitoring, and Tuning
SSL
Security
XML
Web Services
JMX
Logging and Internationalization
SNMP