9.2 Authentication

 <  Day Day Up  >  

Authentication is the process by which the identity of a subject is verified . Authentication must be performed in a secure fashion; otherwise , a malicious entity may impersonate legitimate entities to gain access to a system.

Unlike the Object Management Group's CORBA and Microsoft's Component Object Model (COM) and Distributed Component Object Model (DCOM), JAAS authentication and authorization are not performed every time a Java method is called. The principal-based Java authentication and authorization processes are integrated with the previously existing J2SE authentication and authorization mechanisms. Authentication and authorization routines must be called explicitly.

An important feature of JAAS is that it can be configured to support a wide variety of authentication mechanisms. For example, the authentication mechanism could require

  • An account name or user name , and a password

  • The ability to prove identity information that only the subject could produce, such as data signed using a private key or a value from a hardware security device

  • A biometric measurement, such as a fingerprint or an iris scan comparison

9.2.1 Pluggable Authentication via LoginModule s

An authenticator is a security component responsible for authenticating the identity of a Subject . In JAAS, authenticators are Java classes implementing the LoginModule interface in package javax.security.auth.spi . LoginModule s are supplied by authentication technology providers. As one of the goals of JAAS is to have a pluggable authentication mechanism, the LoginModule framework methods are generic enough to allow all authentication mechanisms to work and simple enough so that complexity need not be a hindrance to authentication mechanism providers.

Each LoginModule must implement the four authentication methods in the javax.security.auth.spi.LoginModule API: login() , commit() , abort() , and logout() . As shown in Listing 9.1, these methods have no parameters, and a Java return type of boolean true if the method succeeded or false if the LoginModule should be ignored. If the method failed, a javax.Security.auth.login.LoginException will be thrown.

Listing 9.1. The LoginModule Interface
 public interface LoginModule {    void initialize(Subject subject,       CallbackHandler callbackHandler, Map sharedState,       Map options);    boolean login() throws LoginException;    boolean commit() throws LoginException;    boolean abort() throws LoginException;    boolean logout() throws LoginException; } 

This approach certainly succeeds in not requiring authentication providers to add constraints to their current interfaces but still leaves the issue of how to provide additional configuration information when required. Additional information is supplied to the LoginModule via the initialize() method, which takes four arguments.

  1. The subject argument is the instance that will be updated by the LoginModule s with Principal s and credentials during login and logout.

  2. The callbackHandler argument provides implementation- and environment-specific information needed to satisfy a particular LoginModule . When it needs to communicate with the user ”for example, to ask for a user name and password ”a LoginModule does not do so directly. The reason is that there are various ways of communicating with a user, and it is desirable for LoginModule s to remain independent of the various types of user interaction. Rather, the LoginModule invokes a javax.security.auth.callback.CallbackHandler to perform the user interaction and obtain the requested information, such as the user name and password. An application typically provides its own CallbackHandler implementation.

    If it needs environment information to authenticate the user, a LoginModule passes the CallbackHandler handle() method an array of appropriate javax.security.auth.callback.Callback s, such as a NameCallback for the user name and a PasswordCallback for the password. The CallbackHandler performs the requested user interaction and sets appropriate values in the Callback s. Then, the LoginModule can examine the array of instantiated Callback s to extract the necessary information. For example, if a user name and password are needed, the LoginModule can examine the Callback array to see whether it contains a javax.security.auth.callback.NameCallback and a javax.security.auth.callback.PasswordCallback. If so, it will use these Callback objects to obtain the authentication information.

  3. The sharedState Map argument allows multiple LoginModule s to share state information.

  4. The options Map argument passes in configuration information from the JAAS LoginModule configuration file (see Section 9.2.2 on page 296). It is a set of key/value pairs for each of the optional arguments specified in the LoginModule configuration file for the LoginModule .

The kind of proof necessary for authentication depends on the security requirements of a particular resource and the enterprise security policies. To provide such flexibility, the JAAS authentication framework is designed around the concept of configurable authenticators . This architecture allows system administrators to configure, or plug in , the authenticators appropriate for the security requirements of the deployed application. This configuration is typically specified on the command line when the application is started or can be specified with a call to System.setProperty() . The configuration location can also be specified in the form of a URL in the security properties file in the lib/security directory.

The JAAS architecture also enables applications to remain independent of underlying authentication mechanisms. Therefore, as new authenticators become available or as current authentication services are updated, system administrators can easily replace or add authenticators without having to modify or recompile existing applications. This allows greater portability of applications, as the authentication implementations are not hard-wired into the applications. Porting an application to a new platform does not require rewriting the application. Instead, the JAAS LoginModule configuration file is modified to specify the authenticators for the platform, and the LoginContext class in package javax.security.auth.login handles the rest of the details of calling the configured authenticators. No application coding changes are required. The LoginContext class represents a Java implementation of the plug-in framework. The LoginContext consults the LoginModule configuration file to determine the authenticators, or LoginModule s, to be used by an application. As we see in Section 9.2.2 on page 296, the first argument to the LoginContext constructor is the name of the stanza , which describes the set of LoginModule s to be used, and the second argument is a CallbackHandler instance.

JAAS also supports the notion of stacked authenticators . This means that an application may be configured to use more than one LoginModule . For example, one could configure both a Kerberos LoginModule and a smart-card LoginModule for an application. The JAAS authentication framework ensures that either all the configured LoginModule s succeed or none succeed via a two-phase process. It is the responsibility of the LoginContext performing the authentication to ensure this all-or-nothing behavior.

  1. In the first, or login, phase of authentication, the LoginContext 's login() method invokes the specified LoginModule s' login() methods and instructs each to attempt only the authentication. For example, each LoginModule will prompt the user for a user ID and password pair and verify it. The authentication status is saved in each LoginModule as private state information. Once finished, each LoginModule 's login() method either returns true when the authentication succeeded or false when this LoginModule should be ignored, or throws a LoginException to specify an authentication failure. In the failure case, the LoginModule must not retry the authentication or introduce delays. The responsibility of such tasks belongs to the application. If the application attempts to retry the authentication, the LoginModule 's login() method will be called again.

  2. In the second, or commit , phase, each configured LoginModule is instructed to formally commit or abort the authentication process. To accomplish this, the LoginContext calls either the commit() method or the abort() method for each of the configured LoginModule s. The commit() method for each LoginModule gets invoked when the overall authentication succeeded. The abort() method for each LoginModule is invoked when the overall authentication failed. If the LoginContext determined that authentication succeeded, the commit() method of each LoginModule that successfully authenticated a Principal associates the appropriate authenticated Principal and credential objects with the Subject . Conversely, if either login phase fails, the LoginContext calls the configured LoginModule 's abort() method to abort the entire authentication process. Each LoginModule is instructed to clean up any state ”for example, erase a password ”that had been associated with the attempted authentication.

In general, a LoginModule in a LoginModule configuration file stanza has one of the following four attributes:

  1. required . The LoginModule is required to succeed. If it succeeds or fails, authentication still continues to proceed down the LoginModule list.

  2. requisite . The LoginModule is required to succeed. If it succeeds, authentication continues down the LoginModule list. If it fails, control immediately returns to the application; authentication does not proceed down the LoginModule list.

  3. sufficient . The LoginModule is not required to succeed. If it does succeed, control immediately returns to the application; authentication does not proceed down the LoginModule list. If it fails, authentication continues down the LoginModule list.

  4. optional . The LoginModule is not required to succeed. If it succeeds or fails, authentication still continues to proceed down the LoginModule list.

The overall authentication succeeds only if all required and requisite LoginModule s succeed. If a sufficient LoginModule is configured and succeeds, only the required and requisite LoginModule s prior to that sufficient LoginModule need to have succeeded for the overall authentication to succeed. If no required or requisite LoginModule s are configured for an application, at least one sufficient or optional LoginModule must succeed. A stanza with a single or stand-alone LoginModule exhibits identical behavior, regardless of the attribute associated with it.

In addition, it is possible to specify a space-separated list of LoginModule -specific option keys and values that are passed directly to the underlying LoginModule s. Option keys and values are defined by the LoginModule itself. The options control the behavior of the LoginModule instance. For example, a LoginModule may define options to support debugging and testing capabilities. There is no limit to the number of options a LoginModule may define.

9.2.2 JAAS LoginModule Examples

We examine the richness of the login process through a series of three LoginModule s (Section 9.2.2.1 on page 301, Section 9.2.2.2 on page 305, and Section 9.2.2.3 on page 308). But first, we provide a sample Main program that shows the context in which LoginModule s are used (Listing 9.2).

Listing 9.2. Main.java
 package jaasexample; import java.security.Principal; import java.util.Iterator; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; /**  * Sample application used to demonstrate how to use JAAS  * LoginModules.  */ public class Main {    /**     * The main method of this application.     *     * @param args an array of Strings to be passed on the     *        command line. The first argument tells the     *        LoginContext which LoginModule stanza to use     *        when selecting LoginModules.     */    public static void main(String[] args)    {       (new Main()).run(args);    }    /**     * This method performs the actual LoginModule     * demonstration.     *     * @param args an array of Strings to be passed on the     *        command line. The first argument tells the     *        LoginContext which LoginModule stanza to use     *        when selecting LoginModules.     */    public void run(String[] args)    {       boolean succeeded; // did the login succeed?       LoginContext loginContext = null;       try       {          // the first argument tells the LoginContext which          // LoginModule stanza to use when selecting          // LoginModules. The second argument is the          // CallBackHandler to be used by all of the          // LoginModules.          loginContext = new LoginContext             (args[0], new DemoCallbackHandler());          loginContext.login(); // attempt login(s)          succeeded = true; // login(s) succeeded       }       catch (LoginException le)       {          succeeded = false; // login(s) failed.       }       System.out.println("Login succeeded? " + succeeded);       if (! succeeded)          return;       // The LoginContext created a Subject.  The       // LoginModules populated it with Principals and       // credentials.       Subject subject = loginContext.getSubject();       showPrincipals(subject.getPrincipals());       showCredentials("Public credentials:",          subject.getPublicCredentials());       showCredentials("Private credentials:",          subject.getPrivateCredentials());       // The next line performs some action that requires a       // Subject.       doSomethingInteresting(subject);       // Attempt to perform a logout on all of the       // LoginModules       try       {          loginContext.logout();       }       catch (LoginException le)       {          le.printStackTrace();       }    }    /**     * Subclasses can override and do something interesting     * here that requires a Subject.     * The default implementation does nothing.     *     * @param subject is the authenticated Subject.     */    protected void doSomethingInteresting(Subject subject)    {       // do something with the Subject    }    /**     * Show all of the Principals, and the classes that     * implement the Principals, contained in the Set passed     * as an argument.     *     * @param principals is a Set of Principals to display.     */    public static void showPrincipals(Set principals)    {       System.out.println("Principals");       Iterator principalsIter = principals.iterator();       if (! principalsIter.hasNext())          System.out.println("   none.");       while (principalsIter.hasNext())       {          Principal principal =             (Principal) principalsIter.next();          System.out.println("Principal: " +             principal.getName());          System.out.println("    Class: " +             principal.getClass().getName());       }    }    /**     * Show all of the credentials, and the classes that     * implement the credentials, contained in the Set     * passed as an argument.     *     * @param title a String to display before printing the     *        credentials.     * @param credentials is a Set of credentials to     *        display.     */    public static void showCredentials(String title,       Set credentials)    {       System.out.println(title);       Iterator credIter = credentials.iterator();       if (! credIter.hasNext())          System.out.println("   none.");       while (credIter.hasNext())       {          Object credential = credIter.next();          System.out.println("   Value: " + credential);          System.out.println("   Class: " +             credential.getClass().getName());       }    } } 

To enable flexibility in selecting an appropriate set of LoginModule s, a java.lang.String is passed to the LoginContext constructor. In this example, the String is obtained as the command-line argument to the Main application and is applied as an index into the LoginModule configuration file to select the stanza describing the appropriate LoginModule (s) to be used for authentication. By using a configuration file and an indexing mechanism, it is possible to change the stanza, or the set of selected LoginModule s, without requiring modification of the application. This flexibility is demonstrated in the three scenarios in this section. The beauty of this approach is that the LoginModule s can be platform specific, but the application code requesting the authentication can remain platform neutral and can be upgraded without requiring changes to the application.

The missing pieces of the Main application are the details of the CallbackHandler instantiated in Main. DemoCallbackHandler , shown in Listing 9.3, is an implementation of a CallbackHandler , providing the function sufficient to demonstrate the LoginModule examples in this chapter.

Listing 9.3. DemoCallbackHandler.java
 package jaasexample; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; 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; /**  * A minimal CallbackHandler class that will process  * NameCallback and PasswordCallback objects.  */ class DemoCallbackHandler implements CallbackHandler {    /**     * Process the Callback objects passed to the     * CallbackHandler, which then calls this method when     * CallbackHandler.handle() is called.     *     * @param callbacks is the array of Callback objects to     *        be processed.     */    public void handle(Callback[] callbacks)       throws IOException, UnsupportedCallbackException    {       for (int i = 0; i < callbacks.length; i++)       {          if (callbacks[i] instanceof NameCallback)          {             // Prompt the user for a user ID             NameCallback nameCallback =                (NameCallback) callbacks[i];             System.err.print(nameCallback.getPrompt());             System.err.flush();             nameCallback.setName((new BufferedReader                (new InputStreamReader(System.in))).                readLine());          }          else             if (callbacks[i] instanceof PasswordCallback)             {                // Prompt the user for sensitive                // information                PasswordCallback pwCallback =                   (PasswordCallback) callbacks[i];             System.err.print(pwCallback.getPrompt());             System.err.flush();             pwCallback.setPassword((new BufferedReader                (new InputStreamReader(System.in))).                readLine().toCharArray());             }             else                throw new UnsupportedCallbackException                   (callbacks[i],                   "Unrecognized Callback");       }    } } 
9.2.2.1 First Scenario

The first step in the authentication process is to create a LoginContext , passing in the name of a LoginModule configuration file stanza and a CallbackHandler . The LoginContext reads the LoginModule configuration file and locates the configuration stanza corresponding to the first argument to the LoginContext 's constructor. The LoginContext , then, instantiates all the LoginModule s specified in the configuration file stanza. Listing 9.4 shows a simple LoginModule configuration file stanza, called Demo1. Note the location of the semicolons. Semicolons terminate LoginModule entries and stanzas.

Listing 9.4. Demo1 LoginModule Configuration File Stanza
 Demo1 {    jaasexample.Demo1LoginModule required    debug=true    succeeded=true; }; 

When Main.main() calls loginContext.login() , the LoginContext finds the Demo1 stanza and instantiates Demo1LoginModule , specifying that this is required to succeed for the LoginContext 's login() method to succeed. Two options, debug and succeed , with their values, are defined for the Demo1LoginModule . The meaning of these options and their value is LoginModule specific. The option names and their values are passed to the LoginModule via the options argument of the initialize() method. Listing 9.5 gives the code for Demo1LoginModule .

Listing 9.5. Demo1LoginModule.java
 package jaasexample; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; /**  * A basic LoginModule that records the arguments passed to  * the initialize() method, prints debug trace messages, and  * determines whether the methods should succeed.  * Other than initialize(), other methods return default  * values.  */ public class Demo1LoginModule implements LoginModule {    // To fill in Principals & credentials    Subject subject;    // To acquire challenge/response info    CallbackHandler callbackHandler;    // State shared with other LoginModules    Map sharedState;    // Key/value options from config file stanza for this    // LoginModule instance    Map options;    // config file debug option value    boolean debug = false;    // config file succeeded option value    boolean succeeded = false;    /**     * Initialize this LoginModule; in particular, record the     * <code>debug</code> and <code>succeeded</code> options     * for this LoginModule instance as specified in the     * LoginModule configuration file. If not specified in     * the configuration file, <code>debug</code> will be     * false, and <code>succeeded</code> will be true.     *     * @param subject is the Subject to be constructed by the     *        LoginModules associated with the LoginContext     *        that instantiated this LoginModule.     * @param callbackHandler is a CallbackHandler that     *        processes the Callback objects for challenge/     *        response.     * @param sharedState is a Map of state shared between     *        LoginModules associated with the LoginContext     *        that instantiated this LoginModule.     * @param options is a Map that defines the options     *        in the LoginContext configuration file for this     *        LoginModule.     */    public void initialize(Subject subject, CallbackHandler       callbackHandler, Map sharedState, Map options)    {       // Save the arguments for later use       this.subject = subject;       this.callbackHandler = callbackHandler;       this.sharedState = sharedState;       this.options = options;       // Get the debug option specified in the       // configuration file.       String debugValue = (String) options.get("debug");       debug = "true".equalsIgnoreCase(debugValue);       // See if the succeeded option was specified in the       // configuration file.       String succeededValue =          (String) options.get("succeeded");       succeeded =          ! "false".equalsIgnoreCase(succeededValue);       if (debug)          System.out.println("Demo1LoginModule.initialize");    }    /**     * Default login routine that returns the     * <code>succeeded</code> value. If <code>debug</code> is     * <code>true</code>, a progress message is printed.     *     * @return the <code>succeeded</code> value.     */    public boolean login() throws LoginException    {       if (debug)          System.out.println("Demo1LoginModule.login: " +             succeeded);       return succeeded;    }    /**     * Default logout routine always returns     * <code>true</code>. If <code>debug</code> is     * <code>true</code>, a progress message is printed.     *     * @return the boolean <code>true</code>.     */    public boolean commit() throws LoginException    {       if (debug)          System.out.println("Demo1LoginModule.commit");       return true;    }    /**     * Default abort routine that returns the     * <code>succeeded</code> value. If <code>debug</code> is     * <code>true</code>, a progress message is printed.     *     * @return the boolean <code>true</code>.     */    public boolean abort() throws LoginException    {       if (debug)          System.out.println("Demo1LoginModule.abort: " +             succeeded);       return true; // return true if the abort has succeeded    }    /**     * Default logout routine always returns     * <code>true</code>. If <code>debug</code> is     * <code>true</code>, a progress message is printed.     *     * @return the boolean <code>true</code>.     */    public boolean logout() throws LoginException    {       if (debug)          System.out.println("Demo1LoginModule.logout");       return true;    } } 

The initialize() method saves state information, such as the debug and succeed options. As is shown in Listing 9.6, when debug and succeed are both set to true , the Demo1LoginModule.login() method succeeds, and the progress through the Demo1LoginModule 's methods is written to System.out , including the eventual call to the logout() when Main.main() calls loginContext.logout() .

Listing 9.6. Running Main with Demo1LoginModule
  java -Djava.security.auth.login.config==jaasexample/   jaasexample.config jaasexample.Main Demo1  Demo1LoginModule.initialize Demo1LoginModule.login: true Demo1LoginModule.commit Login succeeded? true Principals    none. Public credentials:    none. Private credentials:    none. Demo1LoginModule.logout 

Although not shown here, if the succeed option is changed to false , the login() fails, and an Exception is thrown.

Note that this scenario's LoginModule configuration file, jaasexample/jaasexample.config , is specified on the command line as the value of the java.security.auth.login.config system property. Alternatively, the value can be set via a call to java.lang.System.setProperty() or in the configuration specified in the security properties file, java.security , in the lib/security directory.

9.2.2.2 Second Scenario

The second scenario builds on the first scenario. The LoginModule of this example, Demo2LoginModule , extends from Demo1LoginModule . The Demo2 LoginModule stanza is shown in Listing 9.7.

Listing 9.7. Demo2 LoginModule Configuration File Stanza
 Demo2 {    jaasexample.Demo2LoginModule required    debug=false; }; 

The source code for Demo2LoginModule is shown in Listing 9.8.

Listing 9.8. Demo2LoginModule.java
 package jaasexample; import javax.security.auth.callback.Callback; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.    UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.login.FailedLoginException; /**  * Demonstrate a login() method that uses Callback objects to  * perform a trivial challenge/response (user ID/password)  * and validate them using hard-coded values.  * All other methods are inherited from Demo1LoginModule.  */ public class Demo2LoginModule extends Demo1LoginModule {    String userName = null;// the authenticated user name    // hard-coded user ID and password for validation    private String referenceName = "demo";    private char[] referencePw = {'d', 'e', 'm', 'o'};    /**     * Using Callback objects and the CallbackHandler, prompt     * the user for a user ID and password and see if they     * match the hard-coded values found in this class.     *     * @return the boolean <code>true</code> if the     *         authentication succeeds.     * @throws LoginException if authentication fails.     */    public boolean login() throws LoginException    {       super.login(); // Optionally print progress message       // the default is that authentication failed       succeeded = false;       if (callbackHandler == null)          throw new LoginException("No CallbackHandler " +             "to get info from the user");       // Prompt for user ID and password via       // the CallbackHandler passed to initialize()       Callback[] callbacks = new Callback[2];       NameCallback nameCallback =          new NameCallback("Name: ");       callbacks[0] = nameCallback;       PasswordCallback passwordCallback =          new PasswordCallback("Password: ", false);       callbacks[1] = passwordCallback;       String name = null;       char[] password = null;       try       {          // Prompt for user ID and password          callbackHandler.handle(callbacks);       }       catch (java.io.IOException ioe)       {          // Login failed          throw new LoginException(ioe.toString());       }       catch (UnsupportedCallbackException uce)       {          // Login failed          throw new LoginException             (uce.getCallback().toString());       }       // Retrieve the user ID and password from the       // respective Callback objects.       name = nameCallback.getName();       char[] tmpPwd = passwordCallback.getPassword();       // treat a null as an empty (length 0) password       if (tmpPwd == null)          tmpPwd = new char[0];       password = new char[tmpPwd.length];       System.arraycopy          (tmpPwd, 0, password, 0, tmpPwd.length);       passwordCallback.clearPassword();       // check the user ID       boolean nameOk = name.equals(referenceName);       if (! nameOk)       {          password = clearPassword( password );          throw new FailedLoginException("Incorrect name");       }       // Trivial password check       if (password.length != referencePw.length)       {          password = clearPassword(password);          throw new FailedLoginException             ("Incorrect password");       }       // Done with the password, so clear it.       password = clearPassword(password);       succeeded = true;       userName = name;       return succeeded;    }    /**     * Wipe out the password character array and return a     * null character array.     *     * @param password is the char[] to be cleared.     * @return a null character array.     */    private char[] clearPassword(char[] password)    {       for (int i = 0; i < password.length; i++)          password[i] = ' ';       return null;    } } 

The primary difference with Demo1LoginModule is that the login() method now performs the authentication, and the CallbackHandler is used to prompt the user for a user ID and password and see whether they match the values hard-coded in the class. In login() , a NameCallback and a PasswordCallback are instantiated. These objects are processed via the CallbackHandler 's handle() method. Then, the login() method performs a simple authorization test by comparing the user ID and password from the NameCallback and PasswordCallback objects with hard-coded values in the class. It is easy to extend this class to call a user ID registry to perform the authentication. In the remainder of login() , if the user ID or password do not match, a javax.security.auth.login.FailedLoginException is thrown. In all cases, the password is cleared from memory by calling clearPassword() to put blank characters into the password array.

The output from running Main with Demo2LoginModule is shown in Listing 9.9.

Listing 9.9. Running Main with Demo2LoginModule
  java -Djava.security.auth.login.config==jaasexample/   jaasexample.config jaasexample.Main Demo2  Name:  demo  Password:  demo  Login succeeded? true Principals    none. Public credentials:    none. Private credentials:    none. 
9.2.2.3 Third Scenario

The next step after authenticating a principal is to create the associated Principal and credential objects and add them to the Subject passed to the LoginModule . Demo3LoginModule demonstrates this by extending Demo2LoginModule , as shown in Listing 9.10.

Listing 9.10. Demo3LoginModule.java
 package jaasexample; import java.util.Set; import java.security.Principal; import javax.security.auth.login.LoginException; /**  * If a login() succeeds, the commit() method populates the  * Subject by adding Principals and credential objects to the  * authenticated Subject; the logout() method will remove  * them from the Subject. The abort() method will reset  * <code>userName</code>.  * This class extends Demo2LoginModule, which performs the  * authentication via the login() method.  */ public class Demo3LoginModule extends Demo2LoginModule {    // Create two hard-coded Principals to use when    // populating the subject    private Principal principal1 =       new DemoPrincipal("Manager");    private Principal principal2 =       new DemoPrincipal("Agent");    // Create two hard-coded credentials to use when    // populating the subject    private String publicCredential = "Louise";    private String privateCredential =       "Private DB access password";    /**     * When the first phase of the LoginContext.login()     * process succeeds, each of the LoginModules need to     * associate Principal and credential objects with the     * Subject.     * This commit() method adds Principals     * <code>principal1</code> and <code>principal2</code>,     * public credential <code>publicCredential</code> to the     * public credentials, and <code>privateCredential</code>     * to the privateCredentials for the Subject.     *     * @return the current boolean value of the     *         <code>succeeded</code> variable.     */    public boolean commit() throws LoginException    {       if (debug)          System.out.println("Demo3LoginModule.commit: " +             succeeded);       // Add 2 Principals and 2 credentials to the Subject       Set principalSet = subject.getPrincipals();       principalSet.add(principal1);       principalSet.add(principal2);       subject.getPublicCredentials().add(publicCredential);       subject.getPrivateCredentials().          add(privateCredential);       return succeeded;    }    /**     * During logout processing, remove the state (Principals     * and credentials) added by the commit() method to the     * Subject, and remove <code>userName</code>.     *     * @return the boolean <code>true</code>.     */    public boolean logout() throws LoginException    {       if (debug)          System.out.println("Demo3LoginModule.logout");       // Remove the Principals and credentials added by       // commit() and clear userName       Set principalSet = subject.getPrincipals();       principalSet.remove(principal1);       principalSet.remove(principal2);       subject.getPublicCredentials().          remove(publicCredential);       subject.getPrivateCredentials().          remove(privateCredential);       userName = null;       return true;    }    /**     * Reset the state set up by the login() method since the     * login process failed. In this implementation,     * <code>userName</code> is reset, and the value of the     * <code>succeeded</code> variable is returned.     *     * @return the current boolean value of the     *         <code>succeeded</code> variable.     */    public boolean abort() throws LoginException    {       if (debug)          System.out.println("Demo3LoginModule.abort");       userName = null;       return succeeded;    } } 

The notable differences with the Demo2LoginModule code are that commit() adds Principal and credential objects to the Subject being logged in. The abort() method removes the reference to the user name. The logout() method removes from the Subject the Principal s and credentials that were added to the Subject by commit() .

As we said in Section 9.1 on page 289, a credential can be an object of any type, whereas the principals must implement the Principal and Serializable interfaces. The Principal s must implement the Serializable interface because Subject is Serializable . In Demo3LoginModule , the public credential is the String object "Louise" , and the private credential is the String object "Private DB access password" . Demo3LoginModule associates two Principal s with the Subject : principal1 , with name "Manager" ; and principal2 , with name "Agent" . Both of these Principal s are instance of the DemoPrincipal class, whose code is shown in Listing 9.11.

Listing 9.11. DemoPrincipal.java
 package jaasexample; import java.io.Serializable; import java.security.Principal; /**  * The Principal class used by Demo3LoginModule.  * This class must implement Principal and Serializable.  */ public class DemoPrincipal implements Principal, Serializable {    private String name; // need this for method getName()    /**     * Create the Principal instance, remembering the name of     * the principal.     *     * @param principalName is a String representing the     *       principal's name.     */    public DemoPrincipal(String principalName)    {       // Remember this for the getName() method       name = principalName;    }    /**     * Return the principal's name.  Required by the     * Principal interface.     *     * @return the String representing the name of the     *        principal.     */    public String getName()    {       return name;    }    /**     * Return the principal's name in a formatted String.     *     * @return the principal's name in a formatted String.     */    public String toString()    {       return "DemoPrincipal: " + name;    }    /**     * Return the hash code for this object.     *     * @return the int value representing the hash code for     *         this object.     */    public int hashCode()    {       return name.hashCode();    }    /**     * Return the boolean <code>true</code> when the parameter     * is the same object as this object; otherwise return     * the boolean <code>false</code>.     *     * @return the boolean <code>true</code> when the     *         parameter is the same object as this object.     *         Otherwise return the boolean     *         <code>false</code>.     */    public boolean equals(Object o)    {       return this == o;    } } 

Surprisingly, the credentials (both public and private) do not need to be Serializable , even though they are part of the Subject . For security reasons, the credentials are not serialized with a Subject . If they were, an attacker could serialize a Subject and then extract the public and private credentials. If the credentials were to include a password or cryptographic key, security would be compromised. Specifically, in the J2SE V1.4 implementation of Subject , the fields that hold references to the credentials are marked as transient so that they will not be serialized.

Note that the methods hashCode() and equals() are overridden because the Principal objects are stored in a Set within the Subject .

The Demo3 LoginModule configuration file stanza is shown in Listing 9.12.

Listing 9.12. Demo3 LoginModule Configuration File Stanza
 Demo3 {    jaasexample.Demo3LoginModule required    debug=true; }; 

In Main.run() , the LoginContext is used to retrieve information about the Subject , along with its Principal s and credentials. The output from running Main with Demo3LoginModule is shown in Listing 9.13.

Listing 9.13. Running Main with Demo3LoginModule
  java -Djava.security.auth.login.config==jaasexample/jaasexample.config jaasexample.Main Demo3  Demo1LoginModule.initialize Demo1LoginModule.login: true Name:  demo  Password:  demo  Demo3LoginModule.commit: true Login succeeded? true Principals Principal: Manager     Class: jaasexample.DemoPrincipal Principal: Agent     Class: jaasexample.DemoPrincipal Public credentials:     Value: Louise     Class: java.lang.String Private credentials:     Value: Private DB access password     Class: java.lang.String Demo3LoginModule.logout 

The Subject 's Principal objects can be used for authorization purposes. This will be demonstrated in Section 9.3.

 <  Day Day Up  >  


Enterprise Java Security. Building Secure J2EE Applications
Enterprise Javaв„ў Security: Building Secure J2EEв„ў Applications
ISBN: 0321118898
EAN: 2147483647
Year: 2004
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net