http://www.developer.com/

Back to article

Securing J2EE Applications with a Servlet Filter


February 3, 2005

Web applications oftain contain both public and private resources. It is necessary to ensure that those key areas that are restricted to the public remain off limits to even the craftiest of users. This is a common problem and there are many different solutions. In this article, I will show how a Servlet Filter can serve as a simple, unobtrusive agent that will help to safeguard data when developing J2EE web applications.

A Few Options

When discussing the topic of security, there are two main categories to consider. Authentication refers to verifying a certain user is in fact who they say they are. Authentication is handled typically via a username and password login. For most sites, a login page utilizes SSL (Secure Socket Layer) over HTTP (Hyper Text Transfer Protocol). Once authentication is complete, you need to perform authorization. Authorization is concerned with ensuring that what a user accesses conforms to their permission set. In other words, they should only see what they are authorized to see.

Along with many third-party tools, J2EE provides some built-in support for security. In the deployment descriptor of an application (web.xml), for example, you can declaratively configure an application for authentication and authorization (see options available under the <security-constraint> element). For in-depth information on how to use these features, consult the Java Servlet specification. One potential problem with this approach is that implementing some of these features also requires actions specific to a Servlet container, making them not entirely portable. Another option for security is the Java Authentication and Authorization Service (JAAS). JAAS consists of APIs to authenticate and authorize in a pluggable, platform-neutral fashion. JAAS is a bit newer and its use is not as widespread as those previously mentioned.

Of course, coding a solution yourself is always an option too. This is not always desirable, depending on the complexity of the problem you are dealing with. However, if your application has rather unique or complex security requirements, you will probably require a custom solution. Below, I will demonstrate how Java Servlet Filters can help craft a solution that works across Servlet containers.

Filter Review

Servlet Filters were introduced in version 2.3 of the Java Servlet specification. If you are completely new to Filters, I suggest reading about them here. I will attempt a high-level overview now. Read about the Intercepting Filter design pattern to better understand the problem Filters try to address.

A Servlet Filter object implements an interface that specifies three methods: init, doFilter, and destroy. The first and the last allow for custom processing at the beginning and end of the object's life cycle. To enable a Filter to initialize itself, an instance of FilterConfig is passed to its init method. The most important method for a Filter object is doFilter. This is where the Filter does its job. It accepts objects of type ServletRequest and ServletResponse (which usually need to be cast to their HTTP versions: HttpServletRequest and HttpServletReponse), and a FilterChain object that contains a 'chain' of Filters to execute (if any) after the current Filter finishes its work.

So how does a Filter object execute? A Filter is specified in the web.xml file inside the <filter> element, along with a corresponding <filter-mapping> that maps a Filter to a request pattern. When this pattern matches a request URI, doFilter on the Filter object is invoked. This allows you, the developer, to intercept the normal application flow and perform whatever tasks you deem necessary. These tasks commonly include logging, compression of response data, and in our case, enforcing application security.

An Authorization Filter

For the purposes of this article, I will assume that the authentication piece of the puzzle has been solved, either custom either by programming or using the declarative support the Servlet specification provides. I will focus on performing authorization for the application. For this, you will use a Filter object. In fact, a Servlet Filter could help solve the authentication problem as well (and handle other security-related tasks, such as storing number of login attempts, locking accounts, and so forth.).

The fictitious application includes public resources, resources for intranet users, and resources that should be accessed only by an administrator. As a result, you need to intercept requests to verify that a user's role or roles allows them to go where they are attempting to go. The Filter intercepts the requests, then, to achieve a clean separation of concerns, invokes a method on the Java class that actually performs the work. There are three objects to look at—an interface, its default implementation, and a Filter object. You'll start with the interface.

The Java interface used for authorization is called, appropriately, AuthorizationManager.

package examples;

/**
 * Manages authorization to the system.
 *
 * @author Michael Klaene
 */
public interface AuthorizationManager {

     public boolean isUserAuthorized(User user,String uri);

}

AuthorizationManager specifies a lone method, public boolean isUserAuthorized(User user,String uri). Method isUserAuthorized accepts a User object, presumably stored in the current HttpSession, and the request URI. It returns true if that user is authorized to view the resource, false otherwise.

A Servlet Filter, AuthorizationFilter, will obtain a reference to an object of type AuthorizationManager and invoke its method. The resources for users and an administrator have been grouped in a subfolder entitled 'restricted'. The following web.xml entries are required for AuthorizationFilter's doFilter method to be invoked each time a 'restricted' resource is requested:

<!--Servlet Filter that handles site authorization.-->
<filter>
     <filter-name>AuthorizationFilter</filter-name>
     <filter-class>examples.AuthorizationFilter</filter-class>
     <description>This Filter authorizes user access to application
                  components based upon request URI.</description>
     <init-param>
        <param-name>error_page</param-name>
        <param-value>../../error.jsp</param-value>
     </init-param>
</filter>

<filter-mapping>
     <filter-name>AuthorizationFilter</filter-name>
     <url-pattern>/restricted/*</url-pattern>
</filter-mapping>
Note: You are configuring your Filter by using an initialization parameter called 'error-page'. A FilterConfig object provides this value in AuthorizationFilter's init method. It tells the Filter where to redirect authorization failures:
private String errorPage;

/**Filter should be configured with an system error page.*/
public void init (FilterConfig FilterConfig) throws ServletException {
       if (FilterConfig != null) { 
           errorPage = FilterConfig.getInitParameter("error_page");
       }

Here is the code for doFilter:

public void doFilter(ServletRequest request,
                     ServletResponse response,
                     FilterChain chain)
       throws ServletException, IOException {
      if(errorPage == null) {
         returnError(request,response,"AuthorizationFilter not
                     properly configured! Contact Administrator.");
      }

      HttpSession session =
          ((HttpServletRequest)request).getSession(false);
      User currentUser = (User)session.getAttribute("user");

      if (currentUser == null) {
          returnError(request,response,"User does not exist in
                      session!");
      }
      else {
          //Get relevant URI.
          String URI = ((HttpServletRequest)request).getRequestURI();

          //Obtain AuthorizationManager singleton from Spring
          //ApplicationContext.
          ApplicationContext ctx =
              WebApplicationContextUtils.getWebApplicationContext(
              session.getServletContext());
          AuthorizationManager authMgr =
              (AuthorizationManager)ctx.getBean("AuthorizationManager");

          //Invoke AuthorizationManager method to see if user can
          //access resource.
          boolean authorized = authMgr.isUserAuthorized(currentUser,URI);
          if (authorized) {
              chain.doFilter(request,response);
          }
          else {
              returnError(request,response,"User is not authorized
                          to access this area!");
          }
      }
}

The top half of doFilter is concerned with checking that the necessary variables are present. The error page should have been stored upon initialization. Also, in this application, a User object should exist in the current session if authentication was performed. A private utility method (returnError) is used to forward to the designated error page with the relevant error text. The remaining code obtains the current URI requested and passes it, along with the User object, to the isUserAuthorizedMethod on an instance of AuthorizationManager. If authorized, the request is forwarded on (possibly to another Filter, if more than one was mapped to this request).

To accomplish all of this, you need to obtain an instance of AuthorizationManager. Ideally, this object should be a single instance to be used across the application. A common way to obtaining a reference to an object instance in this situation would be through JNDI (Java Naming and Directory Interface). I have chosen to create a singleton instance by using an ApplicationContext from the Spring framework as an alternative to JNDI. An ApplicationContext can be thought of as a central repository for application objects. It requires two things. First, you must create a descriptor file under WEB-INF, by default called application-context.xml, to instruct the ApplicationContext to create a single object that implements AuthorizationManager:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<!--Application context -->
<beans>
    <!--Our Authorization Bean -->
    <bean id="AuthorizationManager"
          class="examples.AuthorizationManagerDefaultImpl"/>
</beans>

Secondly, you need to add a Spring listener to web.xml to load the ApplicationContext:

<!--Listener that loads Spring's /WEB-INF/applicationContext.xml
    file.-->
<listener>
    <listener-class>org.springframework.web.context.
                    ContextLoaderListener</listener-class>
</listener>

The library spring.jar must also be in your classpath. Please visit the Spring web site for more documentation on this approach. The point is that, in one way or another, AuthorizationFilter needs to reference a single AuthorizationManager instance. A Spring ApplicationContext (and the objects it contains) can be pulled from the current ServletContext by a utility class:

//Obtain AuthorizationManager singleton from Spring ApplicationContext.
ApplicationContext ctx =
    WebApplicationContextUtils.getWebApplicationContext(
        session.getServletContext());
    AuthorizationManager authMgr =
        (AuthorizationManager)ctx.getBean("AuthorizationManager");

You now have everything wired together, so it is time to look at the details of authorization. Note that, by programming to an interface instead of a concrete class, you have concealed the 'how-to' of authorization. This is a good programming practice allowing AuthorizationFilter to ignore the low-level implementation details (and allow you to reimplement things later without impacting the rest of the application). Class AuthorizationManagerDefaultImpl reads role-to-URI mappings from a system properties file called mapping.properties:

# Properties file with role/uri path pattern mappings.
# {Role}={URI path}
ADMIN=/Filters/restricted/admin/*
USER=/Filters/restricted/users/*

This resembles mappings often found in the web.xml file of an application. These mappings could have come from a database or an XML file. In the current implementation, this file will be read and cached in the object's constructor. It will be necessary to restart the container to change these mappings, but this should not be a significant issue:

//Contains role mappings.
private Properties roleMappings;

/**Load mappings from a properties file on the file system.*/
public AuthorizationManagerDefaultImpl() {
     //Read in properties file containing role mappings...
     this.roleMappings = new Properties();
     try {
          this.roleMappings.load(new FileInputStream(
                                 System.getProperty("file.separator")
                                 +"mapping.properties"));
     }
     catch (Exception e) {
           throw new RuntimeException(e);
     }
}

Below is the implementation of isUserAuthorized that iterates through each mapping to see whether it matches the request URI. If it does, you check to see whether the user has the role associated with that mapping. Once found, you can exit.

/**
 *Returns boolean indicating whether user has the appropriate role
 *for the specified URI.
 */
public boolean isUserAuthorized(User user, String uri) {

    boolean matchFound = false;
    boolean authorized = false;

       Iterator i = roleMappings.entrySet().iterator();

       //Loop through each URI mapping and check user's roles.
       //Exit once match is found.
       while( (!authorized)  &&  (i.hasNext()) ) {
           Map.Entry me = (Map.Entry)i.next();

            //Pattern match.  '*' should be interpreted as a wildcard
            //for any ASCII character.
            String mapPattern =
                ((String)me.getValue()).replaceAll("\\*",".*");
            matchFound = Pattern.matches(mapPattern,uri);

            if(matchFound && user.getUserRoles().contains(me.getKey())) {
              authorized = true;
            }
        }
        return authorized;
    }

You iterate through the roleMappings Properties object (Class Properties extends HashTable). Instead of some complex parsing with String methods, I've used Java's pattern matching capabilities. All occurrences of '*' are replaced with '.*' to effectively wildcard your mappings (".*" allows a match on any preceding ASCII character). When found, you check for that role on the User object. It is important to point out the use of cached objects in the example. Nowhere do you hit a database for this information. Because this logic will execute for every matching request, it needs to execute quickly.

Authorization in Action

I have created an index.jsp page to verify AuthorizationFilter is doing its job. Essentially, it provides links to other pages in the application, one public (not in the restricted subfolder), one for users (restricted/users), and one for an admin (restricted/admin). I have also instantiated a User object(that simply gives itself a role in its constructor). This User object has the 'USER' role, but not the 'ADMIN' role. When you attempt to navigate to the public page and the page in the users section, everything works fine. However, when attempting to access the admin page, you are redirected to the error.jsp, which displays the following text: 'User is not authorized to access this area!'.

One overlooked piece of functionality concerning Filters is their ability to handle not only actual requests to the server, but also server-side includes, forwards, and error redirection. In the 2.3 Servlet specification, only the initial request to the server was processed. In the 2.4 specification, however, you can configure a Filter object so that it intercepts other request dispatching actions. This is accomplished with the <dispatcher> element. Currently, AuthorizationFilter could be circumvented if a developer added a jsp forward directive to an admin resource from a user resource. An updated index.jsp tests some forward and include scenarios. Your web.xml file needs to be updated to specify that the Filter should apply to types 'REQUEST', 'FORWARD', 'INCLUDE', and 'ERROR'(though not tested here):

<filter-mapping>
    <filter-name>AuthorizationFilter</filter-name>
    <url-pattern</restricted/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

The page includeRestricted.jsp attempts to include an admin resource with a single piece of code:

<jsp:include page="restricted/admin/admin.jsp"/>

However, that action will be denied.

Conclusion

In this article, I have demonstrated one way to implement authorization for a J2EE application utilizing a Servlet Filter. This is a simple and unobtrusive way to satisfy authorization requirements. It can be plugged into a new or existing application running on any Servlet container.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date