Struts page-level authorisation

15 September 2006

Peter Hilton

by Peter Hilton

A common requirement for web applications is role-based authorisation, to determine which users can access which functionality and data. This article explains how to use Struts’ support for page-level authorisation.

Web application page-level authorisation

Page-level authorisation involves restricting access to your web application by allowing or denying access to whole pages. This means specifying which roles may access each page, and then assigning roles to users. Page-level authorisation is a kind of coarse-grained authorisation model that is useful for specifying the web site or web application’s high-level authorisation.

For example, you might restrict all ‘account maintenance’ pages to ‘administrator’ users, or if you have separate pages for View Customer Details and Edit Customer Details you might only allow 'editor' users to access Edit Customer Details and other edit pages.

Note that you will also need fine-grained authorisation within each page, but that is a topic for another article.

Java Servlet authorisation

The Servlet Specification defines a role-based authorisation API for declarative and programmatic security. With declarative security, you can specify ‘security constraints’ in the web.xml deployment to limit access to certain URLs in your web application to certain roles. While this works, it is not always convenient to restrict access by URL patterns and to maintain the declarations. This is particularly noticeable in a Struts application, where these declarations would have to duplicate the action path information specified in struts-config.xml.

Note that authentication - determining who the current user is and what roles that user has - is the Servlet container's responsibility, and outside the scope of this article.

Struts authorisation

Struts supports declarative role-based authorisation, by allowing you to add a list of authorised roles to an action mapping definition. For example, to restrict access to a customer page to the 'viewer' and 'editor' roles, you can use:

<action path="/customer" type="CustomerAction" roles="viewer, editor">

These role names are the same kind of role names mentioned in the Servlet API, so in this example, the user can only access the /customer if (request.isUserInRole("viewer") || request.isUserInRole("editor")) is true.

You can also specify this with the equivalent XDoclet syntax, which some people prefer because it keeps the definitions next to the Action code:

/**
 * @struts.action path="/customer" roles="viewer, editor"
 */

Role maintenance problems

The advantage of this kind of declarative authorisation over programmatic authorisation is that you write less code. It is also easy to see which roles have access to a given page: you just look up the page's path in struts-config.xml. This centralisation is also a great improvement on messy programmatic authorisation that scatters lines of code like:

if (request.isUserInRole("viewer") || request.isUserInRole("editor")) {

However, this approach causes problems for role-maintenance. The first problem comes when you want to know which pages a given role can access, and you have to search for occurrences of the role name.

The second, more serious problem is when you want your application to have a user-interface for editing the page-level access for each role. Rather than editing struts-config.xml you might want to store this information in a database.

There are two approaches you can take here: implement programmatic authorisation or use the Servlet API to deal with 'permissions' instead of roles.

Programmatic page-level authorisation

The most natural way to implement programmatic page-level authorisation is in a Servlet filter that grants or denies access by checking whether the current user's authenticate roles include a role that may access the page-being requested. For example, the filter method might look something like:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
   throws IOException, ServletException {

   final HttpServletRequest request = (HttpServletRequest) req;

   // Look up the list of roles that are authorised to view this page.
   final Set<string> roles = application.getRolesAuthorisedForRequest(request);

   // Check whether the current user has one of the required roles.
   boolean isUserAuthorised = false;
   for (String role : roles) {
      if (request.isUserInRole(role)) {
         isUserAuthorised = true;
         break;
      }
   }

   // Allow or deny access.
   if (isUserAuthorised) {
      chain.doFilter(req, res);
   }
   else {
      final HttpServletResponse response = (HttpServletResponse) res;
      response.sendError(403, "Not authorised to access page");
   }
}

This is much simpler with a REST approach where the requested page is completely determined by the request URL, as in this example. Things are more complicated when you have to start looking at session or form data.

Using the Servlet API for permissions

A slightly simpler alternative is for the authentication process to grant ‘permissions’ instead of roles to the current user, where a ‘permission’ is something like ‘access to the customer details page’. To do this with Struts authorisation, have a one-to-one mapping between action paths and permissions, where the single role is really a 'permission' that is the same as the action path.

<action path="/customer" type="CustomerAction" roles="/customer">

To make this work, change the authentication process to grant a role/permission for every page the authenticated user's roles have access to. This means that you have moved the page-to-role mapping from the authorisation process to the authentication process.

Hyperlinks and page-level authorisation

Once you have implemented page-level authorisation it is immediately clear that you do not want your pages to include links to pages that the current user is not authorised to view. Instead of giving the user links to click and then denying access to the linked page, it is better not to render the link.

A good way to do this is to make a JSP tag that is a subclass of org.apache.strutsel.taglib.html.ELLinkTag with the required behaviour. You would use this tag in the same was as the normal Struts html:link tag, but if the user is not authorised to view the destination page then the tag will render its body as unlinked text. For example, your application might display a list of customer names, where each name is a link to an ‘edit customer’ page, but only for authorised users.

It is also useful to add an attribute so that the link is not rendered at all, for links whose contents are commands rather than data. For example, it does not make sense to render a ‘delete this customer’ link as unlinked text if the current user is not authorised to view the 'delete customer' page.