Tuesday, 20 April 2010

Spring Security 3.0 and Spring EL

Among the many improvements in Spring Security 3.0 is the ability to use the Spring Expression Language (SPEL) to secure methods and URLs. This immediately opens up the possibility of more sophisticated "domain object security" on service methods and URLs. To secure service methods, a number of additional attributes have been added that support the SPEL. The most immediately useful is the @PreAuthorize attribute which is similar to the old @Secured attribute in intent but with a bit of SPEL processing thrown in as well. The documentation gives the following example:
@PreAuthorize("#contact.name == principal.name)")
public void doSomething(Contact contact);
The #contact refers to the contact parameter in the method. The tag causes all of the method parameters to be injected as variables into the expression language evaluation context. Variables are referenced using the # prefix. This script basically checks that the #contact variable has a name field that matches the name field in the current principal object (principal is an implicit object that is the current Spring Security Principal implementation). There are3 other attributes that have been added for filtering and post-authorisation. You can also use SPEL to restrict access to URLs.
<http expressions="true>
<intercept-url pattern="/admin*" access="hasRole('admin') and hasIpAddress('192.168.1.0/24')">
...
</intercept-url>
</http>
A great feature of this is that the EL has access to the HttpServletRequest in the EvaluationContext for the web request, meaning that you can peek at request parameters in the EL to grant access based on their values. Another useful feature is being able to check access to a specific URL in the Spring Security taglibs before rendering content.
<sec:authorize url="/admin">

This content will only be visible to users who are authorized to send requests to the "/admin" URL.

</sec:authorize>
These are all very useful features. However, there are a number of problems with the support offered for EL base access control that I have discovered. My immediate instinct was to define EL based access control inside the security context with the <intercept-url /> and then use <sec:authorize url="/xxx" /> in JSPs to render links only when a user has access to them. This works well up to a point. Problems start to occur when you need to evaluate objects in the request in the expression and try to authorize those URLs using the tag. For example, take the following EL definition which checks that the employee id supplied in the request matches the employee id of the logged in principal.
<intercept-url pattern="/timesheetview.htm"
access="principal.employeeId ==
T(java.lang.Integer).valueOf(request.getParameter('employeeId'))" />
This on its own works well enough. If you request the URL in a browser, requests are correctly authorised (or not). However, if you then try to use the <sec:authorize url="/xxx" /> tag in a JSP to check access to this URL, things no longer work. For example, the script below seems like it should work.
<c:url value="/timesheetview.htm" var="url">
<c:param name="employeeId" value="${employeeId}>
<c:url>
<sec:authorize url="${url}">
<a href="${url}">View timesheet<a>
</sec:authorize>
The problem is that the authorize tag does not allow scripting variables in the url attribute. However, there is a further problem which means that even if it did you still can't dynamically check access based on a request parameter. My first attempt at a workaround was this:
<sec:authorize url="/timesheetview.htm?employeeId=123">
<a href="/timesheetview.htm?employeeId=123">View timesheet<a>
</sec:authorize>
This gets around the scripting issue, but fails for a more fundamental reason (above the fact that employee id must be hard-coded). The reason this doesn't work is that Spring Security issues a "dummy" request to the URL to see whether access is denied or granted. The dummy request is made using an instance of org.springframework.security.web.access.DummyRequest which is a partial implementation of HttpServletRequest. Specifically, most operations cause an OperationNotSupportedException - including HttpServletRequest.getParameter(String name). That means that when the EL expression executes, it tries to call request.getParameter() which in turns causes the OperationNotSupportedException. It is in fact impossible in Spring Security 3.0 to use an EL expression in the <intercept-url /> in combination with the <sec:authorize url="xxx"> tag in a situation where the url in question is secured with an expression that accesses the HttpServletRequest - regardless of whether the parameter is supplied in the request or not. These problems mean that this exciting feature falls quite short of expectations when used in what seems to be a fairly obvious scenario. I'd like to see the DummyRequest be able to support request parameters in a future version that are evaluated based on the contents of the url attribute. This attribute should support scripting variables to enable parameters to be passed in using <c:url> Don't get me wrong - it's still awesome. Spring Security 3.0 is a worthwhile upgrade but there is always room for improvement.

2 comments:

  1. Hi,

    I'm currently playing around with the @PreAuthorize annotation as well and try to figure out how to unit test the EL expression in isolation. Any thoughts?

    ReplyDelete
  2. That's a good idea - I was using the old "trial and error" approach to figuring this out. The classes for evaluating Spring EL should be easy to use to parse EL expression. The difficulty would be mocking all of the web-specific context stuff that is injected when using it in an MVC or servlet context. Will consider it for a future post!

    ReplyDelete