HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

There are myriad approaches to architecting logon and user management in the J2EE environment. This article addresses some alternative J2EE architectures as well as the strategy used by each alternative and the code for implementing each solution. I'll focus on the end user ­ not the business purpose of many of today's medium- to large-scale Web apps ­ but my primary focus will be on state-of-the-art solutions.

First, some background information on Struts and MVC frameworks: Struts is on its way to becoming a de facto standard for developing medium- to large-scale Web-based applications on the Java platform. Struts fits nicely into the J2EE technology stack by filling some gaps not addressed by the servlet/JSP standard or the EJB standard. A framework can isolate the developer from the ever-changing J2EE APIs and make it easier to get J2EE applications completed in a shorter time frame. (Although, caveat emptor, Struts is an ever-changing API.) A framework does this by taking care of the low-level plumbing and difficult programmatic challenges involved with building a J2EE application. Basically, a good framework will do the dirty work for you. For more details, see the References section at the end of this article.

Let's begin with the end user at the client in Figure 1 or 2 and work our way toward the server shown in bold outline at the right side of these figures. Figure 1 shows a filters-based design. Filters sit between the end client and the requested resource and are able to intercept the request before the resource sees it. Figure 2 shows another design where front-end processing is incorporated into the application; in the latter case, changes to one component mean changes to the application proper. Struts apps can be built either way.

Filter Strategies
Before launching into filter strategies, here's a brief introduction to filters ­ a feature introduced in the Servlet 2.3 API. The contents of the filter are written in Java and are thereby flexible and easy to maintain without touching the application code. As shown in Figure 1, filters can be chained and are able to intercept a request before the resource sees it, and can intercept the response on the way back to the client, including generating their own request object for the final resource to see and/or generate their own response to the client.

Filters, like any other Java class, can interact with external resources such as LDAP or database servers. One practical use of filters includes the ability to transform output to suit the presentation needs of the different client devices sending it requests. Whether the app needs to support XML translated to HTML or accept a WAP cell phone that's translated to WML, filters can be used. Filters are pluggable, so you can make changes to them without affecting your application. Another advantage is that they can make decisions about authentication and the like without necessarily burning up CPU cycles running the ServletController, Action, or ActionForm methods of your Struts application.

Filters can be implemented in two ways: the Standard Filter Strategy (see Figure 1) or with the Intercept Filter Design Pattern found in the Struts org.apache.struts.action.RequestProcessor class (see Figure 2). Its processPreprocess() method provides preprocessing and postprocessing of a client Web request (and response). A detailed account of this J2EE pattern is available in Core J2EE Patterns, and filters in general in Professional JSP 2.0 (see References).


Figure 1


Figure 2

The standard filters strategy can be used in front of Struts or any other Web app, while the Struts framework strategy ­ as its name implies ­ cannot. In either case, adding the javax.servlet.http.HttpSessionListener interface to the story, as illustrated in Listing 1, helps you deliver login management that runs with browsers that don't have cookies or JavaScript enabled. Naturally, the latter is not an option when you're using Struts (or other) tags that produce JavaScript that needs to run in the downloaded page. Before the introduction of the HttpSessionListener Interface in the Servlet 2.3 API (J2EE 1.3), you had to write elaborate session managers to keep track of user sessions or to determine when a particular session was invalidated. Now you can use this one minimal piece of code.

A Chain of Pluggable Intercepting Filters
After users are authenticated (Filter2 in Figure 1), but before they and, possibly, their roles are looked up in a DBMS (Filter4 in Figure 1), the users' sessionID and username are inserted into a Hashtable of active users (filter 3 in Figure 1), unless the users are already there, in which case access to the resources of the app is denied and the users are notified that they are already logged in. The logged-in users are removed from the Hashtable when they log out or are inactive for the duration of the session timeout (typically 30 minutes).

Implementations of HttpSessionListener (see Listing 2) are notified of changes to the list of active sessions in a Web application. To receive notification events, the implementation class must be configured in the deployment descriptor for the Web application. Using this relatively new interface, the onus of keeping track of user sessions and notification is on the container, not code written by the application developer. The session timeout, declared in the Web app's descriptor file (web.xml), is the event used in Listing 2.

The helper class in Listing 3 manages the Hashtable. To produce the same results in a Struts app as those described above for a filter, the code in Listing 1 is written in a LoginAction or helper class, while Listings 2 and 3 are used unchanged. One final word on filters: just as you must be careful with multiple threads in Java servlets, be careful not to violate any thread-safety practices with filters. The servlet container may send concurrent threads to a single instance of a filter class, and you must ensure that you don't do anything to cause problems between the threads. In other words, there should be no client-specific data stored in instance variables. Local variables are fine, just as they are in Java servlets, because they're stored on the stack rather than the heap.

Authentication and Authorization
Typically, a username and password will be read from a form and compared against server-side values known to your Web app. There are two points of integration: during the login process and when the client requests a resource (usually a URL). At each of these points, the application can defer to Java Authentication and Authorization (JAAS) classes to perform the action. JAAS is a standard extension to the Java 2 SDK v 1.3 and is a part of v 1.4. Traditionally, Java 2 provided code source­based access controls (access controls based on where the code originated from and who signed the code). However, it also lacked the ability to enforce access controls based on who runs the code. JAAS provides a framework that augments the Java 2 security architecture with such support. JAAS authentication is performed in a pluggable fashion that permits Java applications to remain independent from underlying authentication technologies.

Of particular interest to the developer of medium- to large-scale Web apps, moreover, is the direct support for the standard JAAS that Struts 1.1 offers. In Struts 1.1, you can specify security roles on an action-by-action basis as a way to prevent unauthorized users from finding a way to execute an action.

With JAAS, new or updated authentication technologies can be plugged into both Struts and other Web applications without requiring modifications to the application. Applications enable the authentication process by instantiating a LoginContext object, which in turn references a Configuration object to determine the authentication technology, or LoginModule, to be used in performing the authentication. Typical LoginModules may prompt for and verify a username and password or use a smart card reader plugged into a computer's USB port. Others may read and verify a voice or fingerprint sample. Authentication for a particular identity can be provided by a Netware server, an Oracle database, LDAP, Windows .NET, a Unix server, or a simple application server­specific XML file.

Permissions are the heart of authorization; they control access to resources. However, the JAAS permissions are built on top of the existing Java security model. This model is good for controlling access to resources like sockets and files, but has no concept of URLs. Thus, to apply JAAS to a Web application, a new permission class must be created.

If your authentication mechanism changes (say from a database to LDAP) you can change it without modifying your business code. With some servers, simply change an XML file to point to your new JAAS/Authentication module and restart. Depending on your application, you can let the application vender provide a prepackaged JAAS infrastructure where you specify the details of authentication and authorization declaratively using configuration files.

The programmatic security described earlier is useful when declarative security alone is not sufficient to express the security model of your application. The security requirements of your application can also be declared in such a way that security considerations can be satisfied during application configuration. The declarative security mechanisms used in an application are expressed in a declarative syntax in a document called the deployment descriptor (web.xml). An application deployer then employs container-specific tools (or configuration files directly) to map the application requirements that are in the deployment descriptor to security mechanisms that are implemented by J2EE containers.

Last, while beyond the scope of this article, Single Sign On (SSO) is a small step away once you have JAAS and LDAP in place.

Of course, you don't have to use JAAS at all. As Figure 2 suggests, you can simply hand code "old-fashioned" lookups to LDAP and/or database servers. Or, in a Struts environment, you can provide a comma-delimited list of security-role names allowed to request a particular action. These names are then available programmatically with the ActionConfig.getRolesNames() method.

Substituting ADSI and ActiveX
As a Java developer, you may sometimes be able to use some of the functionality found in ubiquitous Microsoft network technology; it can occasionally provide a ready-made and inexpensive solution, particularly in a low-budget intranet environment. First, there's Microsoft's Active Directory Service Interfaces, or ADSI, a programmatic interface to Active Directory domain controllers, as well as to Windows NT and NetWare 3, 4, and later versions. What's of interest here is that almost all access to Active Directory takes place with LDAP. For the Java developer, ADSI offers almost the full range of access to Active Directory from your scripts written in Microsoft's proprietary Windows-limited Java 2.0 SDK, called J/Direct. But the litigious debate between Microsoft and Sun Microsystems will probably limit your interest in using J/Direct. However, the good news is that you have always been able to access a subset of Microsoft's "LDAP server" from code written with Sun's JDK.

As an aside, there's the possibility of getting a user's Windows 2000 public credential using an ActiveX control built into Microsoft's IE. Here's all the code that you need:

var WinNet = new ActiveXObject("WScript.Network");
var userName = WinNet.UserName;

However, client-side authentication is problematic. A simple way to defeat this ActiveX-based approach is to "view source" in your browser, save it to a text file, and modify the JavaScript to always return the desired user name. However, there's sometimes a simple way around this shortcoming: simply disable access to the browser's menu.

Monitor and Control
You may need to monitor and control incoming requests for Web resources, whether or not you choose to use filters ­ blocking an authorized user's attempt to submit a duplicate request after he or she has unintentionally pressed a Submit button twice, clicked the Back button and resubmitted a form, or tried to access certain views out of order by returning to previously Bookmarked pages. You could remedy these situations on your own by coding J2EE's Front Controller pattern, Command and Control strategy or, more efficiently, by using an encapsulation of everything you need in two methods of Strut's org.apache.struts.action.Action class ­ isTokenValid() and saveToken() ­ as shown in the following snippet:

if (isTokenValid(request, true)) {
// your code here
return mapping.findForward(Messages.success);
}
else{
saveToken(request);
return mapping.findForward(Messages.other)
}

When this code is run, a unique token, generated using the session ID and the current time, is stored as an attribute in the session, and, in parallel, the JSP responsible for generating the HTML creates a parameter in the request as a hidden form field when you use the Struts <html:form> tag.

The isTokenValid() method determines if there's a token stored in the user's current session, and the value submitted as a request parameter with this action matches it. This method returns false under any of the following circumstances:

  • No session associated with this request.
  • No token saved in the session.
  • No token included as a request parameter.
  • The included token value does not match the token in the user's session.
If there's a match, we're certain the request submission is not a duplicate. If the tokens don't match, the request will go through the else block where a new token will be saved in the user's current session, creating a new session if necessary.

In your rush to exploit these elegant solutions, don't forget to consider earlier, sometimes much-simpler techniques that we've "always" had available. For example:

if (username==null && password==null)
{
((HttpServletResponse)response).sendRedirect("/Login.jsp");
}

can eliminate the bookmark problem. And, setting a flag and then disabling a button after first use can eliminate the problem caused by pressing a Submit button twice.

Validation of User Inputs
Finally, no article on user behavior would be complete without at least a brief mention of validating user inputs. Validation is another one of those "growth industries" from which you can pick and choose from a number of possible solutions ­ client and server side. While a regular user of the former, I'll limit myself here to the subject of pattern matching on the server side. In the past, to pattern match using the Java programming language required the use of the StringTokenizer class with many charAt substring methods. This often led to complex or messy code. But, J2SE version 1.4 contains a new package called java.util.regex, enabling the use of regular expressions. The Apache Software Foundation now provides org.apache.regexp.RE, an efficient lightweight regular expression evaluator/matcher class. It enables sophisticated matching of strings against a pattern and extraction of parts of the match. Then there's what I'll call the "industrial-strength validation alternative" provided for Struts apps, the Struts Validation framework. With it, you can wind up writing a lot of boilerplate XML for every form and for every field of every form. On the other hand, the validator does reduce the amount of validation logic you have to write.

Conclusion
While you have a great many state-of-the-art bells and whistles available to you for consideration in your next medium- to large-scale Web app, you also have some tried, true, and frequently overlooked resources sitting unused in your browsers and at your servers. While I've concentrated on monitoring and controlling the end user, the same philosophy ­ of always looking for a simpler route ­ should also be applied to any design aspects of your app.

References

  • Turner, J., and Bedell, K. (2002). Struts Kick Start. Sams .
  • Struts tutorial: http://jakarta.apache.org/struts/ doc-1.0.2/userGuide/index.html
  • Alur, D., et al. (2001). Core J2EE Patterns. Prentice Hall.
  • Struts 1.1 API: http://jakarta.apache.org/struts/api/index.html
  • JAAS in Struts: www.mooreds.com/jaas.html
  • JAAS tutorial: http://java.sun.com/products/jaas/index-10.html
  • Li, S., et al, (2003). Professional JSP 2.0. Wrox Press.

    Author Bio
    Marcia Gulesian is a senior software engineer and health information analyst at Children's Hospital Boston. She is the author of more than 100 technical articles on distributed applications and the systems that support them. [email protected]

    "Alternative Approaches to Architecting Logon and User Management"
    Vol. 8, Issue 3, p. 23

    	
    
    
    Listing 1
    
          String username = request.getParameter("username");
    
          HttpSession session = ((HttpServletRequest)request).getSession(true);
    
          boolean liu = LoggedInUserTable.isLoggedInUser(username);
      
          if (!liu)
          {
           LoggedInUserTable.addLoggedInUser(session.getId(), username);
          }
          else
          {
           String userSessionId = LoggedInUserTable.getSessionId(username);
            if ( userSessionId != session.getId())
            {
             ((HttpServletResponse)response).sendRedirect("/Messages/notice.jsp");
            }
          
          }
    
    Listing 2
    
         public class UserTracker implements  HttpSessionListener {
      
         String sessionId = null;
    
         public void sessionCreated(HttpSessionEvent e) {
       
          }
          
          public void sessionDestroyed(HttpSessionEvent e) {
          LoggedInTable.removeLoggedInUser(e.getSession().getId());
    
          }
    
         }
    
    Listing 3
    
         public class LoggedInUserTable {
    
         /** A Hashtable of all of the active users. */
    
         private static Hashtable LoggedInUsers = new Hashtable ();
    
         public static boolean isLoggedInUser (String username) {
         return  LoggedInUsers.containsValue(username);
         }
    
         public static void addLoggedInUser ( String sessionId, String username) {
         LoggedInUsers.put (sessionId, username);
         }
    
         public static String getSessionId(String username) {
         return (String) LoggedInUsers.get(username);
         }
    
     
    

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.