Authentication using JAAS

by Rahul Bhattacharjee

What are authentication and authorization?

Authentication can defined as the process to confirm the identity of an user. This can be achieved in a variety of ways, e.g. by entering a password, swiping an ID card, or using a biometrical scanner. The user need not be a human being, but can be a computer process.

Authorization can be defined as the process of deciding whether an authenticated user/system is allowed to access a certain resource or perform a certain action or not. A system may have many logical sections/modules, and not all users might have access to all modules. For example, one would not want an employee of a company to be authorized to get into parts of an application related to the company's appraisal system or other confidential data. This is where authorization comes into play. Though the user might have authenticated himself, he might not have sufficient authorization to access certain particular data items.

Both the above –authentication and authorization– are addressed by JAAS.

What is JAAS?

As the name –Java Authentication and Authorization Services– suggests, it provides a framework and an API for the authentication and authorization of users, whether they're human or automated processes. Both parts provide full-fledged capabilities and can be used in small-sized applications as well as enterprise applications, where security is a major concern. JAAS was inspired by PAM (Pluggable Authentication Module); one might say that JAAS is a Java version of PAM. It was introduced as an optional package for use with JDK 1.3, but has been integrated as part of the standard JDK 1.4.

JAAS uses a service provider approach to its authentication features, meaning that it is possible to configure different login modules for an application without changing any code. The application remains unaware of the underlying authentication logic. It's even possible for an application to contain multiple login modules, somewhat akin to a stack of authentication procedures.

In this article we will discuss the authentication part of JAAS in detail, starting with the various classes and interfaces which are involved, followed by a ready-to-run example.

Classes and interfaces

LoginModule (javax.security.auth.spi.LoginModule)

Login modules are written by implementing this interface; they contain the actual code for authentication. It can use various mechanisms to authenticate user credentials. The code could retrieve a password from a database and compare it to the password supplied to the module. It could also use a flat file, LDAP or any other means of storing user information for that purpose. Generally, in enterprise networks all authentication credentials are stored in one place, which might be accessed through LDAP.

LoginContext (javax.security.auth.login.LoginContext)

The login context is the core of the JAAS framework which kicks off the authentication process by creating a Subject. As the authentication process proceeds, the subject is populated with various principals and credentials for further processing.

Subject (javax.security.auth.Subject)

A subject represents a single user, entity or system –in other words, a client– requesting authentication.

Principal (java.security.Principal)

A principal represents the face of a subject. It encapsulates features or properties of a subject. A subject can contain multiple principals.

Credentials

Credentials are nothing but pieces of information regarding the subject in consideration. They might be account numbers, passwords, certificates etc. As the credential represents some important information, the further interfaces might be useful for creating a proper and secure credential – javax.security.auth.Destroyable and javax.security.auth.Refreshable. Suppose that after the successful authentication of the user you populate the subject with a secret ID (in the form of a credential) with which the subject can execute some critical services, but the credential should be removed after a specific time. In that case, one might want to implement the Destroyable interface. Refreshable might be useful if a credential has only a limited timespan in which it is valid.

The process of authentication

The authentication process starts with creating an instance of the LoginContext. Various constructors are available; the example uses the LoginContext(String, CallbackHandler) variety. The first parameter is the name (which acts as the index to the login module stack configured in the configuration file), and the second parameter is a callback handler used for passing login information to the LoginModule. CallbackHandler has a handle method which transfers the required information to the LoginModule. The example uses a very simple handler which saves the username and password in an instance variable, so that it can be passed on during the invocation of the handle method from the LoginModule. It's also possible to create callbacks that interact with the user to obtain user credentials, and transfer that information to the LoginModule for authentication.

An empty Subject is created before the authentication begins. This is passed to all login modules configured for the application. If the authentication is successful, the subject is populated with various principals and credentials.

The login method in the LoginContext is used to start the login process. After its successful completion, the application can retrieve the Subject from the LoginContext using the getSubject() method.

The login process has two phases. In the first phase, the individual login module's login method is invoked, but at this point the principals and credentials are not attached to the subject. The reason being that if the overall login fails, the principals and credentials attached to the subject are invalid, and have to be removed.

If the login process is successful the commit methods of all login modules are invoked, and the individual login modules take care of attaching the appropriate principals and credentials to the subject. If it fails then the abort method would be invoked; that gives the login modules a chance to perform any necessary cleanup.

The login method of the login module should return true if the authentication is successful, false if this module should be ignored, and it throws a LoginException if the authentication fails.

JAAS configuration in detail

Let's take a look at a sample JAAS configuration file.

RanchLogin { com.javaranch.auth.FirstLoginModule requisite debug=true ; com.javaranch.auth.SecondLoginModule required debug=false email=admin@mydomain.com ; };

With this configuration, two login modules have been configured under the name RanchLogin: FirstLoginModule and SecondLoginModule. Each login module is configured with a flag, which decides its behavior as the authentication proceeds down the authentication stack. Other dynamic information related to specific login modules can be passed using key/value pairs. One parameters is supplied to the first login module (debug, and two to the second (debug and email). These parameter values can be retrieved from within the module. The possible flags are:

1) Required – This module must authenticate the user. But if it fails, the authentication nonetheless continues with the other login modules in the list (if any).

2) Requisite – If the login fails then the control returns back to the application, and no other login modules will execute.

3) Sufficient – If the login succeeds then the overall login succeeds, and control returns to the application. If the login fails then it continues to execute the other login modules in the list.

4) Optional – The authentication process continues down the list of login modules irrespective of the success of this module.

An example

A login configuration file is needed, which specifies the login module to be used. The file name is passed as a JVM parameter via a -Djava.security.auth.login.config="JAAS_CONFIG_FILENAME" switch. The following code shows a simple example.

The code triggering the authentication (com.javaranch.auth.Login)

CallbackHandler handler = new RanchCallbackHandler(userName, password);

try {
    LoginContext loginContext = new LoginContext("RanchLogin", handler);
    // starts the actual login
    loginContext.login();
} catch (LoginException e) {
    // log  error (failed to authenticate the user - do something about it)
    e.printStackTrace();
}

Login configuration file (loginConfig.jaas). It ties the name "RanchLogin" (used in the previous paragraph) to the class RanchLoginModule (shown in the next paragraph).

RanchLogin {
    com.javaranch.auth.RanchLoginModule required;
};

Implementation of LoginModule in the class RanchLoginModule

public boolean login() throws LoginException {
    boolean returnValue = true;
	if (callbackHandler == null){
        throw new LoginException("No callback handler supplied.");
    }

    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("Username");
    callbacks[1] = new PasswordCallback("Password", false);

    try {
        callbackHandler.handle(callbacks);
        String userName = ((NameCallback) callbacks[0]).getName();
        char [] passwordCharArray = ((PasswordCallback) callbacks[1]).getPassword();
        String password = new String(passwordCharArray);
        //--> authenticate if username is the same as password (yes, this is a somewhat simplistic approach :-)
        returnValue = userName.equals(password);
    } catch (IOException ioe)  {
        ioe.printStackTrace();
        throw new LoginException("IOException occured: "+ioex.getMessage());
    } catch (UnsupportedCallbackException ucbe) {
        ucbe.printStackTrace();
        throw new LoginException("UnsupportedCallbackException encountered: "+ucbe.getMessage());
    }

    System.out.println("logged in");
    return returnValue;
}

Authentication with a SecurityManager

There's one problem with this code - if a security manager is present (as it is likely to be in applications worth protecting) an exception is thrown. We have to give certain permissions to the code using the policy file:

grant { permission java.util.PropertyPermission "user", "read"; permission java.util.PropertyPermission "pass", "read"; permission java.util.PropertyPermission "java.security.auth.login.config", "read"; permission java.util.PropertyPermission "java.security.policy", "read"; permission javax.security.auth.AuthPermission "createLoginContext.RanchLogin"; };

We have to grant the code AuthPermission with target createLoginContext, so that it is allowed to instantiate a LoginContext object.

To run the code with a security manager we also need to pass a -Djava.security.manager parameter to the JVM, along with the location of login configuration and policy files. Alternatively, it's also possible to modify the default policy file that comes with the JDK directly.

Running the examples

Make sure that jaas.config, policy.config and the jaas-example.jar file are in the same directory. (There are also Ant targets named runCase1, runCase2, runCase3 and runCase4 that will execute these test program with the specified parameters.) The complete, ready-to-run code can be downloaded here.

java -Duser=rahul
     -Dpass=rahul
     -Djava.security.auth.login.config=jaas.config
     -jar jaas-example.jar

Result : Successful, as the username is same as the password.

java -Duser=rahul
     -Dpass=notrahul
     -Djava.security.auth.login.config=jaas.config
     -jar jaas-example.jar

Result : Failed, as the username is not as same as the password.

java -Duser=rahul
     -Dpass=rahul
     -Djava.security.auth.login.config=jaas.config
     -Djava.security.manager
     -jar jaas-example.jar

Same as above, but with a security manager enabled. Result : Failed, as the code doesn't have the required permissions.

java -Duser=rahul
     -Dpass=rahul
     -Djava.security.auth.login.config=jaas.config
     -Djava.security.manager
     -Djava.security.policy=policy.config
     -jar jaas-example.jar

Result : Successful, as the code has been granted all the necessary permissions using the policy.config file.