Thursday, January 15, 2009

An alternative to req:isUserInRole

For me, HttpServletRequest's getRemoteUser() and isUserInRole(role) have always felt like the right place to obtain user information in a web application. My experience with JASIG CAS filter and SecurityFilter has taught me that it is easy to create a servlet filter and drop in a request wrapper (HttpServletRequestWrapper) and create a method to set the remoteUser value. Couple this filter with something like Spring's DelegatingFilterProxy and it is easy obtain user details from some dependency injected external database.

Once the remoteUser value exists in the wrapped request you can acquire the details in a JSP page using JSTL e.g:


<c:out value="${pageContext.request.remoteUser}"/>

or with simple expression language alone (JSP 2.4+), e.g:


${pageContext.request.remoteUser}

The question is how to obtain the role information for a user. Jakarta's Request tag library enables you to make use of role information.


<req:isUserInRole role="admin">
<h1>Hello Admin User</h1>
</req:isUserInRole>

For some reason the request tag library is now deprecated. Of course you can still use it (and it is in the Maven repository) but there is something of a stigma associated with using deprecated code! So I have come up with an alternative. The issue is that it is not easy to invoke methods with parameter arguments from expression language. You cannot simply call:


${pageContext.request.isUserInRole('admin')}

Using expression language you can access scoped variables, arrays, lists and maps. Most of the time this data will be acquired and stored in some scope prior to loading the JSP page (such as in a typical MVC application). However, I have discovered that you can acquire runtime generated responses from a Map using Commons Collection's LazyMap and this technique can be used to effectively perform a user role lookup. I added the following method to my request wrapper (and made sure the request wrapper was publically accessible):


public Map getIsUserInRole() {
Transformer factory = new Transformer() {
public Object transform(Object mapKey) {
return isUserInRole((String) mapKey);
}
};
Map lazyMap = MapUtils.lazyMap(new HashMap(), factory);
return lazyMap;
}

and this means I can now access role information in a way that looks very much like a direct call to request.isUserInRole(role).


${pageContext.request.isUserInRole['admin']}

Using an idea that Matthew Sgarlata had for the request tag library it is even possible to include support for multiple role queries.


public Map getIsUserInRole() {
final String ROLE_DELIMITERS = ", \t\n\r\f";
Transformer factory = new Transformer() {

public Object transform(Object mapKey) {
StringTokenizer tokenizer = new StringTokenizer((String) mapKey, ROLE_DELIMITERS);
boolean result = false;
while (tokenizer.hasMoreTokens() && !result) {
if (isUserInRole(tokenizer.nextToken())) {
result = true;
}
}
return result;
}
};
Map lazyMap = MapUtils.lazyMap(new HashMap(), factory);
return lazyMap;
}
and the expression language becomes:

${pageContext.request.isUserInRole['admin,staff']}

and this can be easily negated:


${!pageContext.request.isUserInRole['admin,staff']}

Sample WAR with source code available (Assuming Java 1.5+ and Maven build "mvn compile war:war").

Update

As an update to this, I am now using the above technqie with what I call a ClearingLazyMap. This class implements the LazyMap interface but does not cache any values when they are fetched. This is necessary in order to pick up the most recent database updates, see also LazyMap that always calls transform, good idea?