Struts, JAAS, Tomcat: getting acquainted

11

The Java Authentication + Authorization Service (JAAS) provides a standard API for making non-standard (i.e. custom) realms available to your application server, see e.g. this article on JavaWorld. To reiterate, a realm is a “database” of usernames, passwords and roles that identify valid users of a (web) application, where multiple roles may be associated to one user. JAAS’s pluggable nature makes it an ideal candidate to integrate legacy realms into your J2EE architecture.

In this post it will be shown how to set up a JAAS-based authentication mechanism for a realm that is disclosed via Hibernate, which in turn talks to SQL server database provided with a JDBC driver. In a subsequent post I’ll discuss authorisation issues.

We applied JAAS + Struts under Tomcat to the application that has been described in Hibernate + Middlegen Roundtrip Development applied, which basically is J2EE-based web application based on a legacy SQL server database, that uses Hibernate as ORM framework and Struts as MVC solution.

First we will present some background info and refs on JAAS, Struts & JAAS and Tomcat separately, thereafter it will be presented how this knowledge was applied to our case.

Introductory material on JAAS

If you are completely new to JAAS, the first four paragraphs of “All That JAAS, Pluggable authentication and authorization services provide many key security benefits for Java applications” by Kevin Jones give a nice description. In addition, you may find the following links useful:

JAAS and Struts

Using JAAS for Authorization and Authentication by Dan Moore starts with a few definitions which are very important to understand when dealing with J2EE security, such as Subject, Principal and Realm.

Thereafter it is shown how to set up JAAS for a Struts based application. More specifically, Dan Moore discusses an example (source can be downloaded) where he uses an implementation of a JAAS LoginModule, that performs authentication against a plain file, that contains usernames, (MD5 encrypted) passwords and roles. This LPGL licensed implementation is offered by the free.tagish.net project, providing implementations for LoginModules against JDBC connected databases, Windows NT Domains and text files, as we just mentioned above.

Authorisation is done by extending the Struts ActionServlet, which would preferrably have been done by using filters, but this is postponed for my post on authorisation.

Dan Moore’s article puts the whole framework basically in place, although we want (actually we need to) modify the location of the JAR file containing the implementation for the LoginModule: since we are going to use Hibernate to extract the authentication information from the database, it is necessary for the LoginModule to “live” inside the web application context.

Tomcat’s container managed security

This section basically boils down to a JAAS-focussed summary of Tomcat Realm HOWTO.

The servlet specification defines how to declare security requirements in the web.xml deployment descriptor, but there is no strandard API for sevlet container < -> web application user/role-related information exchange, hence all solutions are application server specific.

Tomcat 5’s solution: the org.apache.catalina.Realm interface. Currently, 5 different realms are supported:

  1. MemoryRealm, this is the default and perhaps most well-known realm, namely the tomcat-users.xml file.
  2. JDBCRealm, for relational databases accessed via a JDBC driver.
  3. JNDIRealm, for LDAP based realms accessed via JNDI.
  4. JAASRealm, need I say more ;-)
  5. DataSourceRealm, for relational databases accessed via a JNDI DataSource.

Realm configuration is done in the conf/server.xml by adding an XML element. Important: the scope of the realm is defined here!

  • All web applications on all virtual hosts
  • All web applications for this virtual host
  • Only this web application on this virtual host

This is discussed in detail in this part of the HOWTO, but this thread on Sun’s Developer Forums is much more detailed! Escpecially step 1 from the HOWTO is worth a complete page: see the LoginModule Developers Guide.

Note: If you are just an application programmer who wants to use an existing JAAS LoginModule (implementation), you don’t need to delve any deeper into this issue, of course! In our case it was needed, since we had to do authentication based on data residing in a (custom) legacy SQL server database.

Putting all the pieces together

Following the recipe from this thread:

  1. Following the LoginModule Developers Guide, we implemented our own HibernateLoginModule. Basically, we took the source code of the free.tagish.net file-based LoginModule, replacing the code where the JDBC realm was involved with Hibernate-based code. Moreover, an AmisHoursPrincipal and AmisHoursPrincipalRole were implemented. This implementation reflects the authentication information of our legacy system, but was not complicated.

    Each user corresponds to a Resource table in the database, and has a many-to-one relationship with the ResourceGroup, which in turn corresponds to a Principal.

    As shown in the example on discussed on the Sun’s developers Forum, AmisHoursPrincipalRole merely extends from AmisHoursPrincipal.

  2. An AuthenticationBusinessComponent was written to carry out the actual task of authentication. This component is completely analogous to Dan Moore’s example (where the LoginContext is used), including the callback handler, which takes care of passing the username/password information provided via the web interface back to the JAAS LoginModule, albeit some small changes, e.g. in our legacy database passwords are stored unencrypted.

    By the way, have things completely under our control now, we may now easily upgrade our legacy application to use MD5 encrypted passwords!

  3. The Struts LogonAction was modified to include a call to the aforementioned authentication business component.
  4. The associated entry was placed in the conf/server.xml


    <Realm className="org.apache.catalina.realm.JAASRealm"
    appName="AmisHours"
    userClassNames="nl.amis.hours.auth.AmisHoursPrincipal"
    roleClassNames="nl.amis.hours.auth.AmisHoursRolePrincipal"
    debug="99"
    useContextClassLoader="false"
    />

  5. The $JAVA_HOME/jre/lib/security/java.security file was changed to include the following JAAS entry:

    login.configuration.provider=com.sun.security.auth.login.ConfigFile
    login.config.url.1=file:${java.home}/lib/security/AmisHours.login

    Of course, there are more options available to make the location of this file known to the JVM/Tomcat.

  6. The file, referred to from the above mentioned java.security, contains the following info:

    DatabaseLogin
    {
    nl.amis.hours.auth.HibernateLoginModule required debug=true;
    };

    Note that the name DatabaseLogin is used in the authentication business component (!):

    public boolean authenticate()
    {
        try
        {
            loginContext = new LoginContext("DatabaseLogin",
                new MyCallBackHandler(username, password));
            loginContext.login();
        }
        catch (LoginException le)
        {
            log.error("LoginException: " + le);
            return false;
        }
        return true;
    }
    
  7. Since our LoginModule “lives” in the application context i.e., the JAAS sources are on equal footing with the remainder of the sources of this (Eclipse) project, all above mentioned Java class files are part of the final WAR file. Therefore Tomcat can find these classes and is happy, and so are we :-)

    Now all that is left is to restart Tomcat, if you didn’t do so already!

Share.

About Author

11 Comments

  1. 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!

  2. your page works just fine in safari.. it would be nice if it stopped telling me that it
    may not work for older versions of mozilla..

  3. Valuable resource! If you could show the source code or at least how you integrated hibernate, that would be much appreciated. :)