Friday, March 29, 2024

Using a CallBackHandler in Java Authentication and Authorization Services (JAAS)

A LoginModule often needs to communicate with the user, for example to ask for a user name and password. In such cases, it does not do so directly, in order to keep LoginModules decoupled from the specific implementation details of the user interaction. Instead, the LoginModule invokes a CallbackHandler to perform the user interaction and obtain the necessary credentials – usually a user name and password.

In the Implementing Java-based User Authentication with JAAS tutorial, we learned how to configure our LoginModule via a Login Configuration file. In today’s follow-up, we’ll be coding the JaasDemoCallbackHandler and LoginModule classes to complete the login process.

The LoginContext

In order to authenticate a user, you first need a javax.security.auth.login.LoginContext. As arguments, it accepts the name of an entry in the JAAS login configuration file as well as a CallbackHandler instance. The LoginContext forwards that instance to the underlying LoginModule (in our case JaasDemoLoginModule). Most applications usually provides their own CallbackHandler implementation. There are also two simple CallbackHandlers – TextCallbackHandler and DialogCallbackHandler – provided in the com.sun.security.auth.callback package as sample implementations.

The JaasDemoCallbackHandler Class

In eclipse, open the JaasDemo project from the last tutorial and create a new class named “JaasDemoCallbackHandler”. It needs to implement the javax.security.auth.callback.CallbackHandler interface so that it overrides its handle() method. Here is what the new class should look like in the editor:

package com.robgravelle.jaasdemo;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

public class JaasDemoCallbackHandler implements CallbackHandler {

        @Override
        public void handle(Callback[] arg0) throws IOException,
                        UnsupportedCallbackException {
                // TODO Auto-generated method stub

        }
}

At the top of the class, declare two fields to store our user ID and password:

private String name;
private String password;

These will be set in the constructor.

Right-click anywhere in the editor and select Source > Generate Constructor using Fields… from the popup menu.

On the Generate Constructor using Fields dialog, make sure that both the name and password fields are checked, check the box beside “Omit call to constructor super()”, and click OK to close the dialog and append the constructor.

public JaasDemoCallbackHandler(String name, String password) {
                this.name = name;
                this.password = password;
}

Our LoginModule passes the CallbackHandler handle() method an array of appropriate javax.security.auth.callback.Callbacks, such as a NameCallback for the user name and a PasswordCallback for the password. The CallbackHandler would then perform the requested user interaction and assign the appropriate values to the Callback objects. Note that in our case we set the values via the constructor so both the name and password values are readily available.

public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof NameCallback) {
                        NameCallback nameCallback = (NameCallback) callbacks[i];
                        nameCallback.setName(name);
                } else if (callbacks[i] instanceof PasswordCallback) {
                        PasswordCallback passwordCallback = (PasswordCallback) callbacks[i];
                        passwordCallback.setPassword(password.toCharArray());
                } else {
                        throw new UnsupportedCallbackException(callbacks[i], "The submitted Callback is unsupported");
                }
        }
}

The JaasDemoLoginModule Class

In the above transaction, the CallbackHandler’s handle() method is invoked by the LoginModule. It obtains the reference to the CallbackHandler from the LoginContext via its initialize() method. It contains other important methods as well, including login() and logout(). We’ll take a closer look at each of these methods as we code them. For now, let’s create the class. Call the new class “JaasDemoLoginModule”. It needs to implement the javax.security.auth.spi.LoginModule interface so that it override its methods. Here is what the new class should look like in the editor:

package com.robgravelle.jaasdemo;

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;

public class JaasDemoLoginModule implements LoginModule {

        @Override
        public boolean abort() throws LoginException {
                // TODO Auto-generated method stub
                return false;
        }

        @Override
        public boolean commit() throws LoginException {
                // TODO Auto-generated method stub
                return false;
        }

        @Override
        public void initialize(Subject arg0, CallbackHandler arg1,
                        Map<String, ?> arg2, Map<String, ?> arg3) {
                // TODO Auto-generated method stub

        }

        @Override
        public boolean login() throws LoginException {
                // TODO Auto-generated method stub
                return false;
        }

        @Override
        public boolean logout() throws LoginException {
                // TODO Auto-generated method stub
                return false;
        }
}

The abort() Method

When the authentication fails for whatever reason, the abort() method for each LoginModule gets invoked. It’s a good place to remove and/or destroy any cached authentication state information, but for this simple example, we’ll leave it as is and return false.

The commit() Method

The commit method checks its privately saved state to see if its own authentication succeeded. If the overall LoginContext authentication succeeded and the LoginModule’s own authentication succeeded, then the commit() method associates the relevant Principals (authenticated identities) and credentials with the Subject. Again, for this simple example, we’ll create a private class field to save our own authentication state and return it from the commit() method.

public class JaasDemoLoginModule implements LoginModule {

        private boolean succeeded = false;

  @Override
        public boolean commit() throws LoginException {
                return succeeded;
        }

The initialize() Method

As you can see below, the initialize() method sets a number of objects. For our purposes, the most important is the CallbackHandler. It should be stored at the class level for future reference in the login() method. It’s a good place to set our class-level authentication information as well.

private CallbackHandler callbackHandler;

public void initialize(Subject subject, CallbackHandler callbackHandler,
                         Map<String, ?> sharedState, Map<String, ?> options) {

        this.callbackHandler = callbackHandler;

        succeeded = false;
}

The login() Method

The authentication process within each LoginModule proceeds in two distinct phases. In the first phase of authentication, the LoginContext’s login() method invokes the login() method of each LoginModule specified in the Configuration file. Our login() method authenticates the user’s credentials received from the CallbackHandler and saves its authentication status in the private (succeeded) class field. Once finished, our login() method must return true upon success or throw a LoginException in the event of failure.

public boolean login() throws LoginException {

        if (callbackHandler == null) {
                throw new LoginException("Oops, callbackHandler is null!");
        }

        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("name:");
        callbacks[1] = new PasswordCallback("password:", false);

        try {
                callbackHandler.handle(callbacks);
        } catch (IOException e) {
                throw new LoginException("IOException calling handle on callbackHandler");
        } catch (UnsupportedCallbackException e) {
                throw new LoginException("UnsupportedCallbackException calling handle on callbackHandler");
        }

        NameCallback nameCallback = (NameCallback) callbacks[0];
        PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];

        String name = nameCallback.getName();
        String password = new String(passwordCallback.getPassword());

  //don't ever do this in a real application!
        if ("userName".equals(name) && "userPassword".equals(password)) {
                succeeded = true;
                return succeeded;
        } else {
                succeeded = false;
                throw new FailedLoginException("Login failed! You may not log in.");
        }
}

The logout() Method

The logout method typically performs the logout procedures, such as removing Principals or credentials from the Subject, or logging session information. At the very least, it should return false.

public boolean logout() throws LoginException {
        return false;
}

Conclusion

Authentication, like all security matters, is never a walk in the park. It tends to take a certain level of difficulty to achieve and may require many inter-locking components. Now that we can perform a basic login process, we are ready to move a little closer to a real-world authentication example by moving the user credentials into a database, employing hashes for validation, and implementing a three-strikes-you’re-out login strategy.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured