Home > JavaEE > Custom LoginModule in 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 /conf/.xml if you cleanly deploy your web application but the following lines should be added between the Context tags:

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=. 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.

To go further:

email
Send to Kindle
Categories: JavaEE Tags: , , , ,
  1. cubaso
    July 22nd, 2009 at 14:21 | #1

    Thanks for useful article!
    Just one little fix for your web.xml: replace index.htm by index.html.

  2. orbit@websig.com
    September 18th, 2009 at 21:15 | #2

    Thought I’d share my experience with getting JAAS/Tomcat 6/Eclipse working. Not everything in here is crucial to your implementation, but I figured this post will at least put it all together. Many articles and blogs left me frustrated with just covering pieces of the solution, and not showing me the necessary connections between them.

    To begin, I installed Java (say version 6.0 or better), and then Eclipse (or MyEclipse, if you like). Then I updated Eclipse with the Subversive plug-in, to access my subversion repository to persist and pull my code onto different machines as I travel.
    (e.g. https://failship.company.com/repo).

    Upon checkout, I created a Dynamic Web Project or whatever name Eclipse provides for a basic web application. Take your time here and step through each of the wizard screens, because some setting changes are subtle and have major pain-in-the-butt ripple effects.

    You’ll have to create the basic LoginModule and Principal implementations to support your custom login code. That information was provided everywhere on the Web. My frustration was in putting it all together. Anyway, out of the scores of resources I used to investigate, I found this one to be pretty concise:

    http://blog.frankel.ch/tech/dev/java/jee/custom-loginmodule-in-tomcat

    If you are going to test with Tomcat 6.0, you must add the following tag to the conf/context.xml file.

    <Loader delegate=”true”/>

    Otherwise, you might get some

    “java.lang.ClassCastException: org.apache.catalina.util.DefaultAnnotationProcessor cannot be cast to org.apache.AnnotationProcessor”

    exception.

    While you have context.xml open, you will have to add a ‘Realm’ for Container Security, to enable our LoginModule secure access. The 1st step is to enable the JaasRealm, typically by pasting in the existing entry as follows:

    <!– Inserted to enable MyAccessLoginModule. You will have to launch tomcat
    with -Djava.security.auth.login.config= pointing to the jaas.config file: –>
    <Realm className=”org.apache.catalina.realm.JAASRealm”
    appName=”MyAccess”
    userClassNames=”com.company.myAccess.realm.MyAccessUserPrincipal”
    roleClassNames=”com.company.myAccess.realm.MyAccessRolePrincipal”>
    </Realm>

    Next, I had to create a jaas.conf file to declare my LoginModule implementation. Notice that this has the same name as the “appName” provided in the <realm> definition above. Here’s the contents of jaas.conf:

    /** Login Configuration for the JAAS Sample Application **/
    MyAccess {
    com.company.myAccess.realm.MyAccessLoginModule requisite debug=true;
    };

    The realm also needs to know how to find the jaas.config file and it does this through the Java system property, set as a JVM argument, for the variable “java.security.auth.login.config”. You can set this, in eclipse by configuring a “Server”, typically through the Window->Preference menu (or in MyEclipse under its preferences). The JVM takes a -D argument, to define the variable, as in:

    -Djava.security.auth.login.config=C:”/Documents and Settings/sandrews/Workspaces/MyEclipse Blue/myAccess-20090902/jaas.config”

    While you’re here, you may want to add any custom defined variables for your web application. My LoginModule read the tomcat-users.xml file as well, so I designated it with the follow arguments:

    -DTomcatUsersXmlFile=C:”/Program Files/Apache Software Foundation/Tomcat 6.0/conf/tomcat-users.xml”

    Again, this last argument was custom to my application, so it’s necessary for your web app.

    Next, edit the data source for your user authentication, as in conf/tomcat-users.xml, to add the role, say ‘myAccess’, and the user, like ‘bsmith’ for example:

    <tomcat-users>
    <role rolename=”myAccess”/>
    <user name=”bsmith” password=”123qwe” roles=”myRole”/>
    </tomcat-users>

    Your web application’s WEB-INF/web.xml should set the security restrictions,
    as in:

    <!– Define a Security Constraint on this Application –>
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>Secured resources</web-resource-name>
    <url-pattern>/jsp/*</url-pattern>
    <url-pattern>/html/*</url-pattern>
    <url-pattern>/index.jsp</url-pattern>
    </web-resource-collection>
    <auth-constraint>
    <role-name>myRole</role-name>
    </auth-constraint>
    </security-constraint>
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>Unsecured resources</web-resource-name>
    <url-pattern>/images/*</url-pattern>
    <url-pattern>/css/*</url-pattern>
    </web-resource-collection>
    </security-constraint>
    <security-role>
    <description>Role required to see admin pages.</description>
    <role-name>myRole</role-name>
    </security-role>
    <!– Define the Login Configuration for this Application –>
    <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>MyAccess</realm-name>
    <form-login-config>
    <form-login-page>/jsp/userLoginForm.jsp</form-login-page>
    <form-error-page>/jsp/userLoginForm.jsp?action=error</form-error-page>
    </form-login-config>
    </login-config>

    Notice that the ‘security-constraint’ wraps a ‘auth-constraint’ which references a ‘role-name’ which maps to a ‘security-role’.

    Lastly, I had to ensure that when the application server loads (not my web application, but the server), it has access to load my LoginModule implementation and supporting classes. Hence I used an ANT script to jar up my *.class files and deploy them under tomcat’s lib directory, as in:

    lib/myAccess.jar

    This was key, and without it, I was left in the dark, not knowing that the WEB-INF/classes deployment (the default) was not enough. The realm is loaded before your web app, and needs to be ready with the data source connections open, etc. Once this was done, I was able to login using my custom LoginModule.

    Again, some of my frustrations were eleviated through the information found at:

    http://blog.frankel.ch/tech/dev/java/jee/custom-loginmodule-in-tomcat

    So some major Thanks go out to that Nicolas Frankel guy, and Good Luck!

  3. robin.bakkerus
    August 4th, 2010 at 13:57 | #3

    Can you publish the code for PlainUserPrincipal and PlainRolePrincipal as well

  4. August 4th, 2010 at 21:50 | #4

    Since they are plain, they both are JavaBeans with a name property (and the appropriate getter/setter).

  5. Reza
    October 12th, 2010 at 12:47 | #5

    I have tried this in Tomcat 6.0 and it failed. It raise HTTP 403 exception.

  6. February 9th, 2011 at 16:46 | #6

    Can you provide more information as to how you are getting the role information and verifying if the user has that role?

    Thanks
    Quadir

  7. Gamma
    August 9th, 2011 at 17:10 | #7

    This is a great resource that I used for an introduction on JAAS (Tomcat 7 / Mac OS X).
    Thanks for the sample,

    Gamma

  8. Florian
    April 1st, 2012 at 11:12 | #8

    Does anyone know if there is a chance to replace:

    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback(“login”);
    callbacks[1] = new PasswordCallback(“password”, true);

    with custom Callbacks (eg OAuthTokenCallback or something like this) and get it running with tomcat? In my planned application i want to realize a container managed login which is not based on username / password.

  9. Smita Verma
    August 23rd, 2012 at 14:34 | #9

    i have done exactly the same thing my authentication succeeded and i have set the userprincipal and role principle.but after the authentication is successful am not able to access the resource.it gives http 403 access denied.i have set the role same as the one mentioned in security constraint and in tomcat-users.xml.pls help

  10. August 23rd, 2012 at 14:42 | #10

    403 means you’re authenticated but don’t have the credentials to access the resource. So I would check the roles, there may be a mismatch or an unpublished file somewhere.

  11. Smita Verma
    August 24th, 2012 at 07:04 | #11

    i have checked the role.i have put the same role in my security-constraint in web.xml and even in my login module i have assigned the same role to the roleprinciple which m adding to the subject on commit.and even in my tomcat-users.xml i have defined that role and given the username and password,these creditentials m entering in my login page too.

  12. Alejandro
    July 10th, 2014 at 22:56 | #12

    hi i tried this example !

    first: thx to explain all this i was a bit confused about this topic and how to set a jaasrealm to use form based auth but now im on it.

    second:
    im having an HTTP status 404

    i followed this example but i made a dynamic web project so
    i can authenticate and everything, but it seems that im setting wrong the url mapping for protected resources or i set wrong the folder path not sure…so i get 404 when i try to get to /admin/admin.jsp

    this is part of my web xml

    FormBasedAuth_JAASRealm

    30

    Admin
    /admin/admin.html

    admine

    admine

    FORM

    /login.html
    /error.html

    this is how project looks like:

    DynamicWebProject
    |__src (folder)
    | |_beans(package)
    | …
    |
    |admin(folder)
    |_admin.html
    |_admin.jsp
    |
    |WebContent(folder)
    |_META-INF(folder)
    |_context.xml

    |_WEB-INF(folder)
    |_ web.xml

    |_login.html
    |_error.html

    on the root i have
    jaas.config

    so can i have some feedback pls thx.

  1. No trackbacks yet.