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 sourcebased 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 serverspecific 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.
mg@theworld.com
"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);
}