Create an anonymous authentication module

Use a CAPTCHA-based authentication module for J2EE Web applications

1 2 3 Page 2
Page 2 of 3
  • Protect the Web resources
  • Generate unique tokens for sessions
  • Implement the JAAS login module
  • Integrate with container's security
  • Test

Let's walk through each of these steps.

Protect the Web resources

The Web resources will be protected using J2EE declarative security. The web.xml snippet that follows below shows the required configuration:

                    

<?xml version="1.0" encoding="ISO-8859-1"?>

< web-app>

< !-- constrain a section of the site --> <security-constraint> <display-name>Anonymous Security Constraint</display-name> <web-resource-collection> <web-resource-name>Protected Area</web-resource-name> <url-pattern>/security/protected/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>anonymous</role-name> </auth-constraint> </security-constraint>

< !-- Default login configuration uses form-based authentication -->

< login-config> <auth-method>FORM</auth-method> <realm-name>Anonymous Form-Based Authentication Area</realm-name> <form-login-config> <form-login-page>/security/protected/login.jsp</form-login-page> <form-error-page>/security/protected/error.jsp</form-error-page> </form-login-config> </login-config> <!-- Security roles referenced by this web application --> <security-role> <role-name>anonymous</role-name> </security-role>

< !-- The Usual Welcome File List --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>

< /web-app>

This snippet shows that:

  • All the resources (JSP pages, servlets) kept under the directory /security/protected are secured by the container
  • 'anonymous'is the only role authorized to view these resources
  • The container accepts only HTTP method GET and POST requests
  • The protected resource can be served over normal HTTP connections
  • Form-based authentication is used; /security/protected/login.jsp and /security/protected/error.jsp serve as the login and error pages, respectively

Once the declarative security is in place, we need to create the login and error pages. Since we also need to associate a CAPTCHA or token with every session, the login.jsp includes a request to the TokenServlet.

Snippet of the login.jsp:

                    

..... ..... <%-- Generates and associates a CAPTCHA --%> <img src="<%= request.getContextPath() %gt;/servlet/AuthToken" alt="Your authentication token"/>

< %-- The form login page --%> <form method="POST" action='<%= response.encodeURL("j_security_check") %>' > <table border="0" cellspacing="5"> <input type="hidden" name="j_username" value="<%= session.getId() %>"> <tr> <th align="right">Challenge:</th> <td align="left"><input type="password" name="j_password"></td> </tr> <tr> <td align="right"><input type="submit" value="Log In"></td> <td align="left"><input type="reset"></td> </tr> </table> </form> ..... .....

The login page uses form-based security and submits data to j_security_check. The j_username input field is hidden and defaults to the user's session ID. The other input field, j_password, is where the user enters the token.

The error page is pretty simple. It simply states that a problem occurred while authenticating the session and provides a link to the login page.

Generate unique tokens for new sessions

After protecting Web resources, we need to provide a mechanism for generating and associating tokens with sessions.

To support multiple token-generation implementations, we use the Abstract Factory pattern. jw.token.factory.TokenFactoryis the abstract factory, and, based on the parameters, it initializes and returns a concrete token factory. If no specific token factory has been requested, it returns a default implementation. There are two implementations of the token factory in the distribution available for download from Resources. The first one is my amateur attempt at creating CAPTCHA's jw.token.factory.SimpleTokenFactory. The second is a more mature implementation provided by the JCaptcha project, jw.token.factory.JCaptchaTokenFactory. Both the token factories return a specific implementation of jw.token.Tokenwhen the getToken() method is invoked. The Token object is a container for the CAPTCHAs and can be rendered as images by calling the getTokenImage.

Figure 5 shows the token factory's object model.

Figure 5. Object model for the token factory and token classes. Click on thumbnail to view full-sized image.

Once the remote client is forwarded to the login page, it calls the jw.jaas.servlet.TokenGeneratorServlet to retrieve a token. The TokenServlet acts as a gatekeeper for all token-generation requests and has been configured in web.xml. It accepts an initialization parameter, tokenFactory, which indicates the token factory to be used. The servlet delegates the call to the factory, obtains the token, and later streams the token as an image to the remote Web client.

Figure 6 shows the token generation sequence.

Figure 6. Sequence diagram for token generation. Click on thumbnail to view full-sized image.

To store tokens associated with the session, we create a cache. The jw.token.AuthenticationTokenCache acts as a repository for all generated tokens and provides an interface to add a new token, query for a token, and finally remove a token associated with a session. The cache is a singleton and uses a synchronized map.

Periodically, we also need to clean up the AuthenticationTokenCache. The jw.token.TokenInvalidationListenerhas been registered as a session listener in the web.xml. Hence, whenever a session is destroyed, the sessionDestroyed() method is called and the token cache is cleared.

Figure 7 shows the object model for the servlet, cache, and invalidation listener.

Figure 7. Object model for the servlet, cache, and the invalidation listener. Click on thumbnail to view full-sized image.

The web.xml showing servlet configuration:

                    

<web-app> ..... .....

< listener> <listener-class>jw.jaas.servlet.TokenInvalidationListener</listener-class> </listener>

< !-- Standard Action Servlet Configuration (with debugging) --> <servlet> <servlet-name>tokengen</servlet-name> <servlet-class>jw.jaas.servlet.TokenGeneratorServlet</servlet-class> <load-on-startup>2</load-on-startup> <init-param> <param-name>tokenFactory</param-name> <param-value>jw.token.factory.SimpleTokenFactory</param-value> </init-param> </servlet>

< servlet-mapping> <servlet-name>tokengen</servlet-name> <url-pattern>/servlet/AuthToken</url-pattern> </servlet-mapping>

..... ..... </web-app>

In the deployment descriptor, the path /servlet/AuthTokenis mapped to the TokenGeneratorServlet. The servlet has been configured to use SimpleTokenFactory.

Implement the JAAS module

The steps for implementing a JAAS login module are well documented in the JAAS Authentication Guide. The login module contains lifecycle methods like initialize(), login(), logout(), abort(), and commit(), which are invoked by a login context. Most containers provide adaptors for plugging in JAAS login modules within their security infrastructures. Tomcat provides JAASRealmfor this purpose. It intercepts the login form submission and creates a callback handler, JAASCallbackHandler, capable of handling two callbacks, NameCallback and PasswordCallback. These callbacks contain the value of j_usernameand j_password input fields, respectively. The callback handler is then passed to the login module to perform authentication.

Figure 8 shows the object model for the login module classes.

Figure 8. Class diagram for JAAS classes

Most of the methods (commit(), abort(), initialize(), logout()) on the login module are self explanatory. Let's look closer at the login() method.

The login() method retrieves the session ID and user's response from the callback handler. It then queries the AuthenticationTokenCacheto retrieve the Tokenassociated with the session. The user's input is compared with the internal state of the Token object. If the comparison succeeds, the user's session is associated with a principal called AnonymousPrincipal. Since the application is interested only in weak authentication, you can consider this similar to naming every user on your site "anonymous."

The login context then commits the process and confirms that the user has logged in. In commit(), we simply add the principal to the subject representing the remote user. The user is now logged into the application with a principal anonymous.

Figure 9 shows login()'s sequence diagram.

Figure 9. Sequence diagram for login. Click on thumbnail to view full-sized image.

Integrate with the container's security

We now need to integrate all the pieces with the container's security infrastructure. This is a server-specific step. Tomcat maintains a configuration file for every deployed Web application. This configuration file is named <applicationname>.xmland is located under the <TOMCAT_HOME>/conf/Catalina/localhostfolder. Replace the file's contents with the following lines:

                    <?xml version='1.0' encoding='utf-8'?>
<Context debug="9" docBase="d:/work/captcha-login/web" path="/clogin">
  <Realm className="org.apache.catalina.realm.JAASRealm" appName="clogin" debug="99" roleClassNames="jw.jaas.AnonymousPrincipal" userClassNames="jw.jaas.AnonymousPrincipal"/>
</Context>
               

In addition, we also require the JAAS configuration directive to associate the login module with the application.

JAAS configuration file:

                    clogin {
   jw.jaas.AnonymousLoginModule required debug=true;
};
               

For convenience, the bundled build scripts automatically create all these configuration entries.

Test

For testing the security module, I have packaged a Web application called Anonymous Bulletin Board (ABB). The bulletin board is a place where users can discuss the topic of the day. The application's homepage lists all the messages posted so far. A user can also contribute by posting messages. The pages that allow posting are protected by declarative security. Users are authorized to post messages only after they enter the right challenge. Figure 10 shows screen shots of the application for this scenario.

Figure 10. Screen shots of the application. Click on thumbnail to view full-sized image.
1 2 3 Page 2
Page 2 of 3