Customize container-managed security with AuthenticRoast

An open source project for simplifying JEE security module development

AuthenticRoast is an open source project that works with the Java™ Authentication Service Provider Interface for Containers (JSR 196) to let you develop custom authentication modules for use with container-managed, declarative security. Joe Sam Shirah shows how AuthenticRoast can minimize configuration impact on Java Enterprise Edition (JEE) containers and greatly reduce coding effort for custom security requirements. A downloadable WAR with demonstration code is provided.

Joe Sam Shirah, Developer and Principal, conceptGO

Joe Sam ShirahJoe Sam Shirah is a principal and developer at conceptGO. While trying to keep clients happy, he has authored several tutorials for developerWorks and the Oracle Java Developer site and is a winner of the Java Community Award. He was the moderator of the developerWorks Java filter forum and has managed jGuru's JDBC, I18N, and Java400 FAQs.



17 January 2012

Also available in Chinese Russian Japanese

A while back, two of my clients requested custom security for web applications. In a new project, the client was primarily an IBM i shop, and it wanted users to access the application via their IBM i logons. The other client wanted an enhancement to an existing application in which certain users would be validated from a secure database, while "standard" users would be validated via existing Lightweight Directory Access Protocol (LDAP) entries.

Both were enterprise productivity applications — rather than public-facing web pages — that ran locally or over a virtual private network. In general, container-managed security had worked well under those circumstances, relieving applications of the burden of filters or other code to check credentials on every page. The issue with the new requirements was that container-managed security is normally driven by realms — user data source mechanisms provided by a container such as Apache Tomcat, IBM WebSphere, or GlassFish. Realms and realm implementations vary and tend to be specific to a given container. These applications would run under GlassFish, which doesn't define any realms for accessing IBM i logons, or for multiple authentication and authorization methods in a single realm.

My goal was to be able to authenticate and authorize users in a custom manner initially, then hand off the security-checking tasks to container-managed security. In this article, I'll present my journey through the Java Authentication and Authorization Service (JAAS) and JSR 196 to a solution using AuthenticRoast, a project licensed under the GNU Lesser General Public License. The article includes a demo WAR that runs under GlassFish (see Download). First, I'll present a brief review of container-managed security, with a focus on form-based authentication.

Container-managed security concepts

Container-managed security is the common term for the declarative security capabilities outlined in the Java Servlet specification (see Resources). When container-managed security capabilities fit an application's needs, they relieve the programmer of a large amount of tedious, error-prone coding. They can also provide consistency for authentication and authorization over multiple applications.

Roles

Roles are defined groups of users, managers, administrators, and so on. Users can be members of multiple groups. Roles and groups greatly simplify security administration and programming compared to trying to control users individually.

The idea behind container-managed security is that users and roles are defined outside the application, and a standard method of acquiring credentials is provided. Web pages that should be secured are also defined externally and are accessible only to authorized users or roles. Container-managed security automatically controls access by using declared roles that are mapped to pages or groups of pages.

The Servlet specification defines four types of authentication:

  • HTTP Basic Authentication
  • HTTP Digest Authentication
  • HTTPS Client Authentication
  • Form Based Authentication

The specification also includes a strong recommendation that application servers implement the Servlet Container Profile of The Java Authentication SPI for Containers, as specified in JSR 196 (see Resources).

Once a user is authenticated, you can use the following HttpServletRequest methods for more fine-grained control of specific page elements:

  • getRemoteUser()
  • getUserPrincipal()
  • isUserInRole(java.lang.String role)

Configuration

Standard container-managed security requires configuration for security constraints, security roles, login, and, optionally, role mapping. Login configuration defines and describes one of the four types of authentication listed above. As you'll see later, AuthenticRoast dispenses with login configuration, instead depending on the class used as an Authenticator to invoke the appropriate authentication type.

Although the latest versions of the Servlet specification introduce annotations and other programmatic methods to define security constraints, I use declarations in the web.xml file here — because they are familiar, in one place, and accessible to application deployers and administrators. For additional elements, subelements, and details not used in this article, see Resources.

Security constraints

The web.xml file's <security-constraint> element declares the pages to be protected and the roles that are allowed access to those pages. Listing 1 shows an example:

Listing 1. <security-constraint> element
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Pages</web-resource-name>
        <url-pattern>/protected/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>boss</role-name> 
        <role-name>mgr</role-name> 
        <role-name>user</role-name> 
    </auth-constraint>
</security-constraint>

The <web-resource-collection> subelement names the collection and defines one or more <url-pattern> elements to be secured. In the example in Listing 1, the collection is called Protected Pages. All pages in the protected directory are subject to the <auth-constraint> subelement, which denotes authorized role names. In the demo code, these roles are boss, mgr, and user. Note that all HTTP method types — GET, POST, and so on — are also subject to the constraint by default; you can specify the inclusion or omission of particular types if necessary.

Security roles

The <security-role> elements in Listing 2 provide role definitions for roles listed in Listing 1's <auth-constraint> subelement:

Listing 2. <security-role> elements
<security-role> 
    <description>boss</description>
    <role-name>boss</role-name>
</security-role> 
<security-role> 
    <description>mgr</description>
    <role-name>mgr</role-name>
</security-role> 
<security-role> 
    <description>user</description>
    <role-name>user</role-name>
</security-role>

Form-based authentication

Form-based authentication allows you to customize the UI for container-managed security. As shown in Listing 3, the <form /> element must use the POST method and an action of j_security_check. The form must also include <input /> elements named j_username and j_password for the user ID and password to be authenticated.

Listing 3. Minimum form-based authentication web-page requirements
<form method="POST" action="j_security_check"> 
<input type="text" name="j_username"> 
<input type="password" name="j_password"> 
</form>

Once your web page meets these minimum requirements, you can add anything necessary to match your application standards. Figure 1, for example, shows the login page for the demo code. It includes a header with an image, labels, and instructions.

Figure 1. dwAuthenticRoastDemo login page
Screen shot of AuthenticRoast demo login page

Logout is typically achieved by invalidating the session. You can also use the HttpServletRequest logout() method in place of or in addition to invalidation to reset the caller identity.

Standard container-managed security also requires a <login-config /> entry in web.xml, as shown in Listing 4:

Listing 4. Example <login-config /> web.xml entry
<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>SomeRealmName</realm-name>
    <form-login-config>
        <form-login-page>/login.jsp</form-login-page>
        <form-error-page>/login-error.jsp</form-error-page>
    </form-login-config>
</login-config>

The <login-config /> entry defines the authentication method and realm. Because this example uses the form-based method, a <form-login-config /> element is necessary to define the pages used for login and errors.

If the realm types supported by the application server meet your needs, this configuration works perfectly. Otherwise, the standard container-managed security reliance on defined realms becomes a real problem. It was this impasse that started my search for a solution.


The problem with JAAS

JAAS is a low-level API — integrated into Java SE at version 1.4 — for developing authentication and authorization modules (see Resources). JAAS was the first solution that came to mind for my projects; it is flexible, allows pluggable modules, and is a standard part of Java SE.

Unfortunately, JAAS was specified before JEE security mechanisms were in place. JAAS works well for stand-alone applications but has no standard integration or binding with servlets or JEE applications. It also bases authority roles on the javax.security.auth.Subject class, which contains multiple principals, whereas container-managed security works with the java.security.Principal class.

In the absence of an integration standard, any support for JAAS by servlet or JEE container vendors is proprietary. In some sense, realms have the same problem, but I decided to resort to proprietary JAAS or my own JAAS hack only if no better, more standard solution was available.


JSR 196 to the rescue?

The Java Authentication Service Provider Interface for Containers is sometimes called JASPI or JASPIC but is better known as JSR 196. Its goal is to specify a standard that allows configuration of custom authentication mechanisms that a compliant container will use to enforce declarative security. The JSR defines profiles for various contexts, including servlets.

I had heard of JSR 196 as far back as 2004, but it had languished in the Java Community Process until the final draft in 2007. At the same time, GlassFish, as the JEE reference implementation, has supported the Servlet Container Profile portion of the JSR from at least version 2.1. JSR 196 is included in the JEE 6 specification (see Resources).

In addition to normal security jargon and the clearly relevant concept of the server authentication module (SAM), JSR 196 uses many sometimes mind-numbing phrases like message authentication modules, message processing runtimes, and Java Authorization Contract for Containers (JACC or Java ACC). Even so, I was willing to go through the process until I zeroed in on two major issues:

  • Each SAM must be configured and effectively integrated into the container, rather than just in or for an application.
  • Following all the steps given in examples — such as the Enterprise Tech Tip "Adding Authentication Mechanisms to the GlassFish Servlet Container" by the JSR 196 tech lead (see Resources) — is a lot of work.

Although I wanted to use JSR 196 methods if possible, I needed a way to minimize the impact and effort involved in implementing SAMs. After another bout of major research, I found an open source project whose goal is exactly that.


AuthenticRoast

The AuthenticRoast project (see Resources) is at version 0.3.3 as of this writing, but it has been used in production for more than three years. My two projects have used it for two years and one year, respectively.

AuthenticRoast consists of three primary JARs with a division of labor into two parts:

Which containers does AuthenticRoast work with?

AuthenticRoast has been tested with GlassFish 2.x/3.x and Tomcat 6. (The Tomcat version is a valve and uses internal calls because Tomcat does not include native support for JSR 196.) The "GlassFish" or JSR 196 version should work with any JEE 6 compliant container, such as WebSphere 8. However, I've found no Internet-published reports of use on servers other than GlassFish.

To use AuthenticRoast with another container, at a minimum you must translate the registration of the base module in GlassFish's Message Security Configuration. You must also convert the httpservlet-security-provider in glassfish-web.xml to your container's equivalent. With WebSphere, the provider association for an application is accomplished using the Map JASPI Provider option from the administration console (see Resources).

If you integrate AuthenticRoast using another container, consider submitting the process to the AuthenticRoast project lead to help guide other developers.

  • AuthenticRoast-API-ver.jar and AuthenticRoast-Impl-ver.jar: Integration with the container can't be avoided, but these JARs:
    • Allow one-time container configuration.
    • Act as a liaison between the container and your application's security modules (authenticators).
  • AuthenticRoast-Extras-ver.jar: Despite its name, you'll use this JAR in most applications (unless you enjoy writing custom code), because it contains the following abstract classes for various types of authentication that can be used with container-managed security:
    • BasicAuthenticator
    • CompositeAuthenticator
    • FormAuthenticator
    • SSLClientAuthenticator
    • TicketAuthenticator

In the remainder of this article and in the demo code, I'll show the usage of the FormAuthenticator class with GlassFish 3.1.1.

Four areas are involved in the AuthenticRoast authentication development process:

  • Container configuration
  • Application configuration
  • Authenticator registration
  • Authenticator code

I'll present them in order.


Container configuration

Configuration for the container (GlassFish in this case) is, happily, a one-time task with AuthenticRoast. The InstallationForGlassfish page on the project wiki (see Resources) is straightforward, and — because there might be changes in the future — I direct you there for the configuration steps. Those directions are for GlassFish 2.x, however, so Figures 2, 3, 4, and 5 point out the differences for GlassFish 3.1.1. Before you start the process, ensure that you have copied AuthenticRoast-API-ver.jar and AuthenticRoast-Impl-ver.jar to the lib folder of the GlassFish installation and restarted the server.

As Figure 2 shows, when you click on Message Security, you can see that the HttpServlet authentication layer already exists. Click on HttpServlet.

Figure 2. GlassFish 3.1.1 Message Security Configurations page
Screen shot of GlassFish 3.1.1 Message Security Configuration

The Edit Message Security Configuration page shown in Figure 3 appears:

Figure 3. GlassFish 3.1.1 Edit Message Security Configuration page
GlassFish 3.1.1 Edit Message Security Configuration page

Click on the Providers tab.

Figure 4 shows why the HttpServlet authentication layer already exists: the GlassFish admin console itself now uses a provider for security. The image also shows that I already created the roast provider. Add it by clicking the New button.

Figure 4. GlassFish 3.1.1 Provider Configurations page
Screen shot of GlassFish 3.1.1 Provider Configurations

Figure 5 shows the entries for the roast provider. These are the same as listed on the AuthenticRoast InstallationForGlassfish wiki page. Leave the Response Policy fields lower in the page (not shown in Figure 5) empty, without selections.

Figure 5. GlassFish 3.1.1 Edit Provider Configuration page
Screen shot of GlassFish 3.1.1 Edit Provider Configuration page

Be sure to save the configuration and restart the server. After this process, you can use AuthenticRoast in your applications.


Application configuration

The web.xml file for an application that uses AuthenticRoast follows the standard for container-managed security as shown in Listing 1 and Listing 2. Notice one exception, however: AuthenticRoast does not require or use the <form-login-config /> element from Listing 4.

As you'll see in the next section, AuthenticRoast requires your authenticator to register with the container, a task that is usually performed by an application listener. Listing 5 shows the entry to define the demo application listener:

Listing 5. Application listener definition entry in web.xml
<listener>
  <listener-class>com.dw.ARDAppInit</listener-class>
</listener>

In the last piece of required configuration, the application informs the container that it uses an httpservlet security provider. The httpservlet-security-provider attribute of the <glassfish-web-app /> element in the container-specific glassfish-web.xml file (called sun-web.xml prior to GlassFish 3.x) accomplishes this task with an entry (roast) matching a defined provider ID:

<glassfish-web-app httpservlet-security-provider="roast" error-url="">

Role mapping is an optional but useful JEE feature. It allows deployers to map different groups or roles to those used in an application. Unfortunately, the entries and locations are container-specific; WebSphere, for example, uses ibm-application-bnd.xml, whereas GlassFish again uses glassfish-web.xml. Listing 6 shows entries to map <role-name /> elements used in the application to deployment-site-specific <group-name />s:

Listing 6. Role mapping in glassfish-web.xml
<security-role-mapping>
    <role-name>boss</role-name>
    <group-name>ARDBoss</group-name>
</security-role-mapping>
<security-role-mapping>
    <role-name>mgr</role-name>
    <group-name>ARDMgr</group-name>
</security-role-mapping>
<security-role-mapping>
    <role-name>user</role-name>
    <group-name>ARDUser</group-name>
</security-role-mapping>

As a result of these entries, when the demo application checks for boss, for example, the container ensures that the ARDBoss role qualifies.


Authenticator registration

ARDAppInit is the demo example's application listener defined in Listing 5. As you can see in Listing 7, it uses the contextInitialized() method to register an instance of the authenticator class at application startup time:

Listing 7. Demo code authenticator registration
package com.dw;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import name.aikesommer.authenticator.Registry;
...
public class ARDAppInit implements ServletContextListener
{
  ...
  public void contextInitialized( ServletContextEvent sce )
  {
    ServletContext sc = null;

    sc = sce.getServletContext();
    // register AuthenticRoast authenticator
    Registry.forContext( sc ).register( new ARDFormAuthenticator() );

    System.out.println( "[ARDAppInit Executed]" );

  } // end contextInitialized

} // end class ARDAppInit

This code is essentially the same in every application; usually the only changes are the package name and setup for your authenticator instance.


Authenticator code

Given the preceding discussion, configuration, and setup, it should come as a pleasant surprise that to enable form-based authentication with your own custom code you generally only need to:

  • Extend the FormAuthenticator class
  • Override at most four — normally only two — of FormAuthenticator's methods
  • Know a little about the SimplePrincipal class

The FormAuthenticator class (in AuthenticRoast-Extras-ver.jar) extends PluggableAuthenticator, which, along with SimplePrincipal, resides in the AuthenticRoast-API-ver.jar. The container, via AuthenticRoast, makes the method calls on class instances.

If you're willing to use the names login.jsp for your login page and login-error.jsp for your login error page, then you're down to dealing with two FormAuthenticator methods. Otherwise, override String getErrorPage() and String getLoginPage() to return your preferred names for those pages.

The two methods you always need to override are:

  • boolean checkCredentials(AuthenticationManager manager, AuthenticationRequest request, String username, String password)

    The checkCredentials() method is responsible for validating the passed credentials — that is, the username and password. Return true if the credentials pass; otherwise, return false.

  • SimplePrincipal loadPrincipal(AuthenticationManager manager, AuthenticationRequest request, String username)

    The loadPrincipal() method should be called only if checkCredentials() returned true to indicate a validated user. The method returns a SimplePrincipal, which is essentially a username and a Set of group names. The constructor takes a username and a String array containing the names of groups or roles associated with that user. The container searches the group names for a match with roles listed in the web.xml <auth-constraint> element (see Listing 1) or as mapped (see Listing 6). Listing 8 shows an example of loading a SimplePrincipal:

    Listing 8. Loading a SimplePrincipal using a static array
    String[] asBossGroups = 
              { "ABCBoss", "ARDBoss", "MVBoss", "RSBoss" }; 
    
    SimplePrincipal sp = new SimplePrincipal( "bossLady", asBossGroups );

Note that, as shipped, these two methods are stateless and unrelated, so access to user and group data often ends up being repeated in each method. You can change that with additional code, of course, but it is something to be aware of.

And that's really it for the authenticator code framework. I think you'll agree that the project could hardly be more simplified than (normally) working with just two methods.


About the demo application

This article's associated demo application (see Download) provides a concrete example of using AuthenticRoast. Other than the pages used for login validation, it has only one page in the protected directory, which just shows the results of calls to getRemoteUser(), getUserPrincipal(), and isUserInRole(java.lang.String role), as shown in Figure 6:

Figure 6. Login result page
Screen shot of AuthenticRoast demo login result page image

Three user/password sets are accepted by the checkCredentials() method:

  • User stevie, password user1
  • User ray, password mgr1
  • User vaughan, password boss1

If the credentials passed are validated, the username and associated array of group names are returned from the loadPrincipal() method in a SimplePrincipal object:

  • asUserGroups for user stevie
  • asMgrGroups for user ray
  • asBossGroups for user vaughan

Listing 9 shows the array contents:

Listing 9. Group arrays used by ARDFormAuthenticator
  private static String[] asBossGroups = 
          { "ABCBoss", "ARDBoss", "MVBoss", "RSBoss" }; 
  private static String[] asMgrGroups = 
          { "ABCMgr", "ARDMgr", "MVMgr", "RSMgr" }; 
  private static String[] asUserGroups = 
          { "ABCUser", "ARDUser", "MVUser", "RSUser" };

Almost all of the remaining code and configuration, including role mapping, is shown in Listings 1 through 7. The exception, as explained above, is that the <login-config /> element shown in Listing 4 is unnecessary and unused in AuthenticRoast.


It's a wrap

Custom modules for container-managed security are needed only occasionally, but the need can be critical at those times. JSR 196 achieved its goal of integration for customized declarative security, but at the cost of sometimes being cumbersome and complicated. The AuthenticRoast project simplifies that process.

Keep in mind that AuthenticRoast's primary upside — you are in complete control — is also its biggest downside: you have to do everything. For example, when I used the GlassFish LDAP realm previously to access Microsoft Active Directory for password and group data, I was done once I concocted the incantation for the search string. When I used AuthenticRoast to add an additional validation process if the LDAP credentials failed, I had to dig in and write the LDAP access code myself.

That downside, however, also applies to working directly with JSR 196 or virtually anything else to create a custom security module. In the end, AuthenticRoast is an open source solution that succeeds in minimizing the configuration impact on JEE containers and greatly reducing coding effort for your custom security requirements.


Download

DescriptionNameSize
Demonstration WAR for this article1j-authenticroast.zip86KB

Note

  1. The Java sources are included in the WAR file. For instruction on using the WAR file, see the readme.txt file included with the download.

Resources

Learn

Get products and technologies

  • AuthenticRoast: Download AuthenticRoast.
  • GlassFish: Find information and downloads for the GlassFish application server.
  • WebSphere Application Server for Developers: Download a no-charge WebSphere offering identical to the production runtime environment your applications will eventually run on.
  • Evaluate IBM products in the way that suits you best: Download a product trial, try a product online, use a product in a cloud environment, or spend a few hours in the SOA Sandbox learning how to implement Service Oriented Architecture efficiently.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source, Security
ArticleID=785083
ArticleTitle=Customize container-managed security with AuthenticRoast
publish-date=01172012