Java Actionpack: How to protect actions

Posted by Berin Loritsch Wed, 08 Aug 2007 11:51:00 GMT

A very common issue dealing with web applications is how to protect a set of URLs so that only people who have the proper permissions can have access. With Java Actionpack, you would accomplish this through the use of filters. Filters can be called before or after actions, and you can be picky on which actions are supposed to be affected by those filters. Java Actionpack uses annotations to mark which methods on a controller are filters. All other public methods are actions. First, let’s look at the annotation we use:

@Filter(position = {Position}, rules = {Rules}, actions = [])

A closer look at the parameters:

position
Position.BEFORE
The filter is called before the actions.
Position.AFTER
The filter is called after the actions.
rules
Rules.ALL
The filter is called for all actions in the controller, no matter what the actions list parameter says.
Rules.INCLUDE
The filter is called for only the actions listed in the actions list parameter.
Rules.EXCLUDE
The filter is called for all actions not listed in the actions list parameter.
actions
The list of action names (method names in the controller) that this filter may affect depending on the rules. The default value is an empty list, so it can be omitted when you use Rules.ALL

The first thing we need to worry about is how many of the actions need to be protected. Is the controller for an administration section where we would need to protect everything? Is the controller for general browsing, but we only need to protect just a couple of methods? Or possibly the controller is for the user self-management so we would need to protect everything except for the login and self registration actions. The only difference is in how we set up the filter. Just to keep things simple, let’s assume the controller is for an administration page.

public class AdminController extends Controller {
    public static final String ADMIN_ROLE = "ADMINISTRATOR" 

    @Filter(position = Position.BEFORE, rules = Rules.ALL)
    public void ensureUserIsAdmin() {
        if ( ! request.isUserInRole(ADMIN_ROLE) ) {
            sendMessage(
                "You don't have permissions to access the administration pages.");

            if ( null == request.getRemoteUser() ) {
                request.setAttribute(ActionPack.CONTINUE, getThisUrl());
                redirectToAction("security", "login");
            } else {
                redirectToAction("home", "index");
            }
        }
    }
}

There’s a lot going on here. First, we have to check if the user is in the administration role. Once we know that, we can decide what to do. First, we send a message (this message is identified by a request attribute that Actionpack will forward through redirects). Next we determine whether the user is logged in or not. If the user is logged in we redirect them to the the action for HomeController.index. If the user is not logged in, we redirect them to the login page, with another special forwarding attribute so that the login section knows to come back to the original request. Don’t worry, the credentials will be checked again after a successful login.

So what’s the deal with request.isUserInRole() ? It’s a standard method on the HttpServletRequest object, and even if you don’t use container authentication, you can still use it in your application. Java Actionpack provides an interface that you use so that it knows what to do with your User object. The interface extends the java.security.Principle interface so that your User object is compatible with the request.getRemoteUser() and request.getUserPrinciple() methods. The user interface also extends java.io.Serializable so that it is compatible with session attributes in servlet engines that are compatible with the newest standards. Lastly, it provides one more method: isInRole(String) . The method is used so that the request wrapper can determine if the user object is in a the requested role.

So where do you put this object we created? The RequestWrapper looks in a session attribute identified by ActionPack.USER, which is the fully qualified class name for the User interface. So your login code will need to create an object that implements actionpack’s User interface, and place it in the session attribute ActionPack.USER and everything works as expected.

Filters are Inherited

When you set up a filter on a controller, you can extend the controller and any filters in the base class also apply to the derived class. For example, if you set up some filters in an abstract ApplicationController used as a base class for all the controllers in your application, those filters are now applied to all the controllers in the system—obeying the rules set up in the filter declaration of course. One possible use for this include sending a “Testing” disclaimer message that only applies when the application is set up on a test server. That message would show up on every page, along with any error messages that use the sendMessage system. I’ve also used it to manage a toolbox that is populated depending on what roles a user has, and possibly which controller the user is accessing (i.e. some tools are only accessible some of the time while others are there all the time). Use your imagination with applying them.