Excluding Pages From Authentication

Java's Servlet spec allows web applications to delegate authentication and authorization to the servlet container, a mechanism known as container-based security. A lot of people use it for in-house applications or web services because it's simple and containers like Tomcat already provide several authentication backends to choose from. There's one common use case though that's a bit tricky: Secure the entire web application except for a couple of special pages (the login page or the homepage come to mind). Judging from my web searches, many developers struggle with this and eventually implement workarounds. In this article I'll present a real working solution.

Without further ado, here's a snippet from a web.xml file that shows how it works:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>Private</web-resource-name>
    <description>Matches all pages.</description>
    <url-pattern>/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
     <role-name>authenticated-user</role-name>
  </auth-constraint>
</security-constraint>
<security-constraint>
  <web-resource-collection>
    <web-resource-name>Public</web-resource-name>
    <description>Matches a few special pages.</description>
    <url-pattern>/index.jsp</url-pattern>
    <url-pattern>/login.jsp</url-pattern>
    <url-pattern>/public/*</url-pattern>
  </web-resource-collection>
  <!-- No auth-constraint means everybody has access! -->
</security-constraint>
<security-role>
  <description>
    A role for all authenticated ("logged in") users. This
    role must be present in the servlet container's user
    management database.
  </description>
  <role-name>authenticated-user</role-name>
</security-role>
<login-config>
  <auth-method>DIGEST</auth-method>
  <realm-name>My Webapp</realm-name>
</login-config>

The example has two security constraints. The first one ("Private") matches all web resources (aka pages) while the second one ("Public") only matches the index page, the login page, and everything below "/public/". Note that the order of the security-constraint elements doesn't matter! Just like with servlet and filter patterns, the most specific match wins. So a request for "/public/foo" matches "Public" because the pattern "/public/" is more specific than "/".

The auth-constraint element specifies which users are allowed access to the matched resource. The role-name given there must refer to a security-role declaration in web.xml and it must also be present in the servlet container's user database. And finally, we also need a login-config definition to tell the container which method of authentication to use (there are several others that are more or less secure than "DIGEST").

Looking at the "Public" security-constraint again, there's one non-obvious thing that you only figure out by reading the Servlet spec very thoroughly. The "Public" security-constraint mustn't specify an auth-constraint element. This means everybody has access, which is exactly what we want.

Searching the web I've seen many different attempts that didn't work:

<auth-constraint /> <!-- access for nobody -->

This means that nobody has access! Similarly, the following doesn't work either:

<auth-constraint>
  <role-name>*</role-name> <!-- access only for declared roles -->
</auth-constraint>

The special "*" role matches all declared roles in web.xml. Unfortunately, there's no magic "anonymous" role for unauthenticated users!

When playing with this example, don't forget to configure your servlet container accordingly. Here's an example for Tomcat's built in user database tomcat-users.xml:

<tomcat-users>
  <role rolename="authenticated-user" />
  <user username="matt" password="mypass"
        roles="authenticated-user" />
</tomcat-users>

It declares a role "authenticated-user" and a user for the role.

social