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 "/*".
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> <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.