Spring Security: accessing Spring beans from your security annotations

This post describes how you can integrate advanced security in your applications using Spring Expression Language and Spring Security.


When it was still called Acegi it was relatively difficult to go beyond role-based security with Spring Security. But it has come a long way since it hit 3.0. Not only has integrating Spring Security with your application become much easier, Spring Security now also offers a very powerful mechanism: using Spring Expression Language (SpEL) from within your security annotations.



In your most basic form this replicates the standard JSR250 @RolesAllowed annotation, for example as follows:



@PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)


No reason to be jumping up and down with excitement just yet. But as you no doubt anticipated using SpEL in your security constraints offers much more than this. Lets say we want to restrict who can access an object based on ownership. We have a Project object, which in turn has a projectOwner property. Using SpEL we can now make sure only that specific user can access the project:



@PostAuthorize("returnObject.projectOwner.username == principal.username")
public Project getProject() {
...
}


The terms “returnObject” and “principal” are Spring Security specific objects. The “returnObject” is the object being returned by the method and the “principal” is the user logged in at that moment.
A similar mechanism can be used to restrict which users can do certain operations on an object. For example we want to ensure that, similar to the example above, only the owner of a project can delete the object:



@PreAuthorize("#project.projectOwner.username == principal.username")
public void deleteProject(Project project) {
....
}


Impressed yet? The best is still to come. Though the above already offers extremely powerful and flexible ways to write security constraints in your application, there are some downsides. First of all as soon as your security constraints become more complex so will your SpEL expressions. And since these are plain strings, you get no IDE support. Another major bummer is that these security constraints cannot be unit-tested. You can of course unit-test the method that you are securing, but then you are testing both the security and the functionality of the method itself in one, which kind of defeats the purpose of a unit-test. Ideal would be if we could have a separate security layer, in Java, which we can call from our security annotations using SpEL. Though it’s a severely under-documented feature of Spring Security, this is indeed possible! Beware that you are now entering bleeding edge territory, as version 3.0 does not support what’s coming next (fixed in 3.1.0.RC1 at the time of writing).



Normal SpEL offers the possibility to access Spring beans from your context using the following syntax:



"#{ @securityBean.getBooleanValue()}"


But this syntax does not work in your Spring security annotations. In the future this will probably be rectified, but at the time of writing the way to access Spring beans from your security annotations is:



“securityBean.getBooleanValue()”


(So without the #{} and @)


This works out to become something as follows:



@PreAuthorize("testBean.getTestBoolean()")
public String getSecurityString() {
return CLEARANCE_GRANTED;
}


Pretty awesome! Our security annotation is referencing a Spring bean and calls getTestBoolean(). Depending on the outcome of this the user is authorized or not to use this method. And taking the earlier concepts of this article into account, this expands into for example something wild as:



@PreAuthorize("testBean.isAuthorizedToDeleteProject(#project, principal)")
public String getSecurityString(Project project) {
return CLEARANCE_GRANTED;
}

Assume at this point that the security method is quite complex, for example something like:



public boolean isAuthorizedToDeleteProject(Project project, User user) {
if (user.hasRole(SystemRoleName.ROLE_PROJECT_MANAGER)) {
if (project.getProjectOwner().equals(user)) {
if (!project.isLocked()) {
if (project.getProjectModifiableDate().after(new Date())) {
if (!project.isPermitRequestDocumentAttached()) {
return true;
}
}
}
}
}

return false;
}


The corresponding SpEL expression would be horrendous! But using the pattern where we call our security layer from SpEL means the SpEL expression remains very manageable while not putting any restraints on your security needs. And as an added bonus we now have a fully unit-testable security method:



@Test
public void testIsAuthorizedToDeleteProject() {
User user = new User();
SystemRole projectManagerRole = new SystemRole(SystemRoleName.ROLE_PROJECT_MANAGER);
user.addSystemRole(projectManagerRole);

Project project = new Project("testProject", user);
project.setLocked(false);
project.setProjectModifiableDate(new GregorianCalendar(2011, 8, 1).getTime());
project.setPermitRequestDocumentAttached(false);

Assert.assertTrue(securityBean.isAuthorizedToDeleteProject(project, user));
}


So what have we achieved? We can now have extremely detailed and complex security logic in Java code, which is unit-testable, callable from within your security annotations and keeping your SpEL expressions nice and clean at the same time. Nice!!

No comments:

Post a Comment