Friday, March 13, 2009

Order of Security Constraints in web.xml for OC4J

In the course of testing and debugging one of my ADF applications, deployed to an OC4J instance of Oracle's Application Server 10g, the security settings I configured were not behaving as I expected. User accounts were authenticating properly, but failing authorization in some circumstances, making it appear as though the login failed. Finding the real cause was very frustrating because I never found a way to log what was actually happening.

I originally set up declarative J2EE security constraints to control access to certain pages, expecting the rules to be applied as specified in the Java Servlet 2.3 specification that requires URL-matching rules to be applied in a given order. It turns out that in OAS 10g, the container-based security provider is still based on an earlier version. Prior to the 2.3 specification, following the rules for authorization was only a suggestion rather than a requirement, and implementers were free to apply the rules as they saw fit. Unfortunately for me, Oracle has not updated this facet of their OC4J container to comply with the specification. Reordering my constraints solved my problem.

What happened? My application's welcome page is home.jsp. My original constraints had some overlapping URLs, specifically a couple pages that should have been accessible by everyone within a directory that was inaccessible to lower-level users. An example from my web.xml file showing pages secured by two roles, REPORTS (reports only) and USERS (everything else). The /faces/pages/home.jsp and /faces/pages/userprofile.jsp pages should be available, but resulted in a login error. Why? According to the way OC4J security implementation, /faces/pages/* is restricted to the USERS role only.



<security-constraint>
  <web-resource-collection>
    <web-resource-name>Users</web-resource-name>
    <url-pattern>/pages/*</url-pattern>
    <url-pattern>/faces/pages/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>USERS</role-name>
  </auth-constraint>
  <user-data-constraint>
    <transport-guarantee>NONE</transport-guarantee>
  </user-data-constraint>
</security-constraint>


<security-constraint>
  <web-resource-collection>
    <web-resource-name>Common</web-resource-name>
    <url-pattern>/common/*</url-pattern>
    <url-pattern>/faces/common/*</url-pattern>
    <url-pattern>/pages/home.jsp</url-pattern>
    <url-pattern>/faces/pages/home.jsp</url-pattern>
    <url-pattern>/menus/*</url-pattern>
    <url-pattern>/faces/menus/*</url-pattern>
    <url-pattern>/pages/userprofile.jsp</url-pattern>
    <url-pattern>/faces/pages/userprofile.jsp</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>USERS</role-name>
    <role-name>REPORTS</role-name>
  </auth-constraint>
</security-constraint>


When a REPORTS user tried to access /faces/pages/home.jsp, the user would actually be logged in, but because authorization to the page failed, the user was presented with the login form again and could never get to the page. To compound the problem, there were no errors in the application server's log file.

According to the specification, the /faces/pages/home.jsp should have been given precedence because it was an exact URL match. Since OC4J security isn't compliant with the specification, it relies on ordering instead.

My solution: by placing the REPORTS constraint first - and thus the exact URL - the security-constraint elements matched the /faces/pages/home.jsp URL first, allowing the REPORTS user to navigate to that page successfully.



<security-constraint>
  <web-resource-collection>
    <web-resource-name>Common</web-resource-name>
    <url-pattern>/common/*</url-pattern>
    <url-pattern>/faces/common/*</url-pattern>
    <url-pattern>/pages/home.jsp</url-pattern>
    <url-pattern>/faces/pages/home.jsp</url-pattern>
    <url-pattern>/menus/*</url-pattern>
    <url-pattern>/faces/menus/*</url-pattern>
    <url-pattern>/pages/userprofile.jsp</url-pattern>
    <url-pattern>/faces/pages/userprofile.jsp</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>USERS</role-name>
    <role-name>REPORTS</role-name>
  </auth-constraint>
</security-constraint>


<security-constraint>
  <web-resource-collection>
    <web-resource-name>Users</web-resource-name>
    <url-pattern>/pages/*</url-pattern>
    <url-pattern>/faces/pages/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>USERS</role-name>
  </auth-constraint>
  <user-data-constraint>
    <transport-guarantee>NONE</transport-guarantee>
  </user-data-constraint>
</security-constraint>


Simple fix, but hard to find. This was yet another example of extremely poor documentation for what is probably a very well-used operation. I hope this solves a mystery for someone else.

2 comments: