How do I create a Login Module?
Author: Deron Eriksson
Description: This Java tutorial describes how to create a Login Module.
Tutorial created using: Windows XP || JDK 1.5.0_09 || Eclipse Web Tools Platform 2.0 (Eclipse 3.3.0)


Page:    1 2 >

In JavaSW, authentication and authorization can be performed by JAAS, the Java Authenication and Authorization Service. Authentication refers to determining a user's identity, while authorization refers to what that user has access to see or do. Much is made of the "pluggable" nature of JAAS authentication, meaning that the authentication can be swapped out without requiring changes to the application.

For detailed information about JAAS, I recommend http://java.sun.com/j2se/1.5.0/docs/guide/security/jaas/JAASRefGuide.html. This tutorial will cover some aspects of authentication using JAAS. For more information, try http://java.sun.com/j2se/1.5.0/docs/guide/security/jaas/tutorials/GeneralAcnOnly.html.

This tutorial will show a simple example of JAAS authentication using a Login Module that we create and a Callback Handler to communicate with the Login Module. The project structure is as follows:

'jaas-authentication-test' project

The JaasAuthenticationTest class contains our main method. First off, our code needs to know what Login Module or Login Modules to use for authentication. The Login Modules are specified in a JAAS configuration file. There are basically two ways to specify where the JAAS configuration file is located. The first way is to specify in your /jre/lib/security/java.security file a login.config.url location (or locations). In this file, you can see an example entry:

login.config.url.1=file:${user.home}/.java.login.config

In the above technique, multiple config files can be specified. You can label them with *.1, *.2, etc.

The second way to specify the location of the JAAS configuration file is to specify a value for the java.security.auth.login.config System property. One way to do that is via a System.setProperty() call, as seen below. You can also do this by specifying a -Djava.security.auth.login.config=FILE_LOCATION value when starting your application.

JaasAuthenticationTest.java

package test;

import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

public class JaasAuthenticationTest {

	public static void main(String[] args) {
		System.setProperty("java.security.auth.login.config", "jaas.config");

		String name = "myName";
		String password = "myPassword";

		try {
			LoginContext lc = new LoginContext("Test", new TestCallbackHandler(name, password));
			lc.login();
		} catch (LoginException e) {
			e.printStackTrace();
		}
	}
}

Our JaasAuthenticationTest class specifies that we'll reference the jaas.config file at the root of our project. This file is shown below.

jaas.config

/** Test Login Configuration **/

Test {
   test.TestLoginModule required testOption=here_is_an_option;
};

The "Test" entry in the config file specifies the name by which the Login Module (or Modules) is referred to. The "test.TestLoginModule" is the Login Module class. The "required" entry is a flag which can be "required", "requisite", "sufficient", or "optional". For more information about these flags and other configuration settings, you can consult http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/Configuration.html. The "testOption=here_is_an_option" is an option name/value pair that can be read in the Login Module. One way that this can be used it to turn a debug mode on or off based on this value.

In our JaasAuthenticationTest class, we create a LoginContext object. This context will use the "Test" Login Module Configuration (shown below), which as we can see in our jaas.config file, maps to our test.LoginModule Login Module class. Notice that the LoginContext constructor takes a Callback Handler class (TestCallbackHandler) as its second argument. The Callback Handler is used to communicate with the Login Module via callback values.

LoginContext lc = new LoginContext("Test", new TestCallbackHandler(name, password));

We can see our TestCallbackHandler class below. It takes a name and password in its constructor. In a web application, we might pass in a request object instead of a name and password, since the request could contain the name and password along with other useful information.

TestCallbackHandler.java

package test;

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;

public class TestCallbackHandler implements CallbackHandler {

	String name;
	String password;

	public TestCallbackHandler(String name, String password) {
		System.out.println("Callback Handler - constructor called");
		this.name = name;
		this.password = password;
	}

	public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
		System.out.println("Callback Handler - handle called");
		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 primary method to implement in the Callback Handler is the handle() method, which takes a Callback array as a parameter. In this example, in the Callback array, it sets the name in a NameCallback and the password in a PasswordCallback. The Login Module will call the Callback Handler's handle method so that it can get the name and password in the Login Module's login() method.

Our Login Module, TestLoginModule, is shown below. It is a very stripped-down version of a Login Module to keep the example simple. For a slightly more involved example with useful method comments, I recommend http://java.sun.com/j2se/1.5.0/docs/guide/security/jaas/tutorials/SampleLoginModule.java.

TestLoginModule.java

package test;

import java.io.IOException;
import java.util.Map;

import javax.security.auth.Subject;
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 javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class TestLoginModule implements LoginModule {

	private Subject subject;
	private CallbackHandler callbackHandler;
	private Map sharedState;
	private Map options;

	private boolean succeeded = false;

	public TestLoginModule() {
		System.out.println("Login Module - constructor called");
	}

	public boolean abort() throws LoginException {
		System.out.println("Login Module - abort called");
		return false;
	}

	public boolean commit() throws LoginException {
		System.out.println("Login Module - commit called");
		return succeeded;
	}

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

		System.out.println("Login Module - initialize called");
		this.subject = subject;
		this.callbackHandler = callbackHandler;
		this.sharedState = sharedState;
		this.options = options;

		System.out.println("testOption value: " + (String) options.get("testOption"));

		succeeded = false;
	}

	public boolean login() throws LoginException {
		System.out.println("Login Module - login called");
		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("Oops, IOException calling handle on callbackHandler");
		} catch (UnsupportedCallbackException e) {
			throw new LoginException("Oops, 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());

		if ("myName".equals(name) && "myPassword".equals(password)) {
			System.out.println("Success! You get to log in!");
			succeeded = true;
			return succeeded;
		} else {
			System.out.println("Failure! You don't get to log in");
			succeeded = false;
			throw new FailedLoginException("Sorry! No login for you.");
		}
	}

	public boolean logout() throws LoginException {
		System.out.println("Login Module - logout called");
		return false;
	}

}

In the TestLoginModule, notice that the testOption value (from jaas.config) is read and displayed in the initialize() method. The login check, not surprisingly, is performed in the login() method. A Callback array is created and passed to the Callback Handler (the Login Module got a reference to the Callback Handler in the initialize() method) via the call to callbackHandler.handle(callbacks). The name and password are retrieved from the Callback array and then checked. If the name and password are correct, the login() method returns true. If they are not correct, the login() method throws a FailedLoginException.

(Continued on page 2)

Page:    1 2 >