/ JAAS, LOGINMODULE, REALM, SECURITY, TOMCAT

Custom LoginModule in Tomcat

Tomcat manages application security through the concept of realm. A realm is a coherent package of name password pairs that identify valid users for a web application.

Tomcat’s default realm is MemoryRealm. This realm reads the famous conf/tomcat-users.xml file and uses it check for name password pair validity. Tomcat also provides realms to check against pairs stored in a database, either through a direct connection, or through a configured datasource. The main disadvantage of these all these realms is that they force you to adopt Tomcat' expected data structure. In most organizations, these constraints will be enough for the architect to rely upon custom or 3rd-party security components.

In order to use your enterprise database structure, you would code a custom realm. Tomcat provides the org.apache.catalina.Realm interface. The drawback of implementing your own realm is that if you change your application server afterwards, all of your code would have been for naught. Yet, if you check Tomcat documentation thoroughly, you will see Tomcat also provides a JAASRealm. JAAS is the Java security feature and enable you to write custom security modules in a portable way. Tomcat’s JAASRealm performs as a adapter between realms and login modules so you only have to write a LoginModule and Tomcat will know how and when to call it.

A LoginModule main method is login(). This is an example of a very basic module implementation:

package ch.frankel.blog.loginmodule;

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

/**
 * Login module that simply matches name and password to perform authentication.
 * If successful, set principal to name and credential to "admin".
 *
 * @author Nicolas Fränkel
 * @since 2 avr.
2009
 */
public class PlainLoginModule implements LoginModule {

    /** Callback handler to store between initialization and authentication. */
    private CallbackHandler handler;

    /** Subject to store. */
    private Subject subject;

    /** Login name. */
    private String login;

    /**
     * This implementation always return false.
     *
     * @see javax.security.auth.spi.LoginModule#abort()
     */
    @Override
    public boolean abort() throws LoginException {
        return false;
    }

    /**
     * This is where, should the entire authentication process succeeds,
     * principal would be set.
     *
     * @see javax.security.auth.spi.LoginModule#commit()
     */
    @Override
    public boolean commit() throws LoginException {

        try {
            PlainUserPrincipal user = new PlainUserPrincipal(login);
            PlainRolePrincipal role = new PlainRolePrincipal("admin");
            subject.getPrincipals().add(user);
            subject.getPrincipals().add(role);
            return true;
        } catch (Exception e) {
            throw new LoginException(e.getMessage());
        }
    }

    /**
     * This implementation ignores both state and options.
     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
     *      javax.security.auth.callback.CallbackHandler, java.util.Map,
     *      java.util.Map)
     */
    @Override
    public void initialize(Subject aSubject, CallbackHandler aCallbackHandler, Map aSharedState, Map aOptions) {
        handler = aCallbackHandler;
        subject = aSubject;
    }

    /**
     * This method checks whether the name and the password are the same.
     * @see javax.security.auth.spi.LoginModule#login()
     */
    @Override
    public boolean login() throws LoginException {
        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("login");
        callbacks[1] = new PasswordCallback("password", true);
        try {
            handler.handle(callbacks);
            String name = ((NameCallback) callbacks[0]).getName();
            String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
            if (!name.equals(password)) {
                throw new LoginException("Authentication failed");
            }
            login = name;
            return true;
        } catch (IOException e) {
            throw new LoginException(e.getMessage());
        } catch (UnsupportedCallbackException e) {
            throw new LoginException(e.getMessage());
        }
    }

    /**
     * Clears subject from principal and credentials.
     *
     * @see javax.security.auth.spi.LoginModule#logout()
     */
    @Override
    public boolean logout() throws LoginException {
        try {
            PlainUserPrincipal user = new PlainUserPrincipal(login);
            PlainRolePrincipal role = new PlainRolePrincipal("admin");
            subject.getPrincipals().remove(user);
            subject.getPrincipals().remove(role);
            return true;
        } catch (Exception e) {
            throw new LoginException(e.getMessage());
        }
    }
}

Once your login module is ready, you have to configure Tomcat to use it. JAASRealms, like any other realms can be configured for the whole Tomcat engine, for a specific virtual host or for a single web application. The simplest is to configure the realm for our application. It may happen in the Servers/Tomcat localhost config/server.xml if you work under Eclipse or in you <TOMCAT_HOME>/conf/<context>.xml if you cleanly deploy your web application but the following lines should be added between the Context tags:

<Realm className="org.apache.catalina.realm.JAASRealm" appName="CustomLogin"
    userClassNames="ch.frankel.blog.loginmodule.PlainUserPrincipal"
    roleClassNames="ch.frankel.blog.loginmodule.PlainRolePrincipal" />

Now that Tomcat knows it should use a JAASRealm, the final step is to configure JAAS itself. The first thing to do is to create a jaas.config file. This file has a very specific structure. Let’s take a look:

CustomLogin {
    ch.frankel.blog.loginmodule.PlainLoginModule
    sufficient;
};

The first line references the login module class and corresponds to appName in the server.xml. The second line tells JAAS how to use this module. There are 4 acceptable values:

Required

This module must authenticate the user. But if it fails, the authentication nonetheless continues with the other login modules in the list.

Requisite

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

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.

Optional

The authentication process continues down the list of login modules irrespective of the success of this module.

Now, you have to reference this file. Just launch Tomcat with this VM property: -Djava.security.auth.login.config=<JAAS_CONFIG>. From this point, if you correctly configure declarative security in you web application web.xml, it will use your login module. Of course, in a real-world case, you would check against a database, a LDAP server or a web-service to authentify your users but this small example provide you with the base to create such a complex code. Sources for this very basic example can be found here.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Custom LoginModule in Tomcat
Share this