Groovy

Getting Started with method security in Grails using Spring Security

This blog post will be about implementing method level security with security expressions in Grails using the Spring Security plugins. I assume you have some basic understanding of the Grails Spring Security Core plugin.

Roles aren’t enough.

When using the Spring Security Core plugin you typically start to configure which roles are required for accessing certain URLs. This configuration can be done by using a configuration map (see below), annotating controller actions with the @Secured annotation or by storing a RequestMap within a database (see: Requestmap Instances Stored in the Database).

Example role configuration:

grails.plugins.springsecurity.interceptUrlMap = [
  // /admin/** URLs can only be accessed by users with role ROLE_ADMIN
  '/admin/**' : ['ROLE_ADMIN'], 
  ...
]

Roles work fine to define simple rules like only admins are allowed to access admin functionality. Unfortunately this often is not enough.

Consider an application in which users can create some kind of content (like comments, news, etc.). A user should be able to edit the content he created later. However, a user should not be able to edit content created by other users. Here, a role could be used to check if a user has general access to the edit content functionality. Unfortunatelly roles aren’t a great help for checking if a certain user is allowed to edit a specific piece of content. This is where security expressions and method security jumps in.

The @Secured annoation

If you used Spring Security (without Grails) before you might remember the @Secured annotation from Spring Security. The Grails Spring Security Core plugin also contains a Grails replacement for this annotation. The grails version (@grails.plugins.springsecurity.Secured) also works on fields while the original (@org.springframework.security.access.annotation.Secured) can only be used on methods. This allows the Grails annotation to be used with fields that contain a closure value:

@Secured(['ROLE_ADMIN']) // only works with @grails.plugins.springsecurity.Secured
def adminAction = {
  ...
}

Additionally the Grails @Secured annotation also supports SpEL expression while the standard Spring Security @Secured annotation only supports role checking (see the documentation for more details).

What about services?

Security constraints are something you typically want to have in your service layer. Unfortunately the Grails Spring Security Core plugin makes the @Secured annotation only available in controllers. To use service level security annotations we have to add the Grails Spring Security ACL plugin. The ACL plugin additionally provides the more sophisticated @PreAuthorize and @PostAuthorize annotations. These annotations can be used to validate access to a method before and after it has been executed (we will see this later). The main purpose of the Grails Spring Security ACL plugin is to provide support for Access Control Lists (ACL) which allow very fine grained control of access rights. Maybe I will write a blog post about the usage of ACLs in the future, but it is definitely out of scope for this post. Here we only use the ACL plugin to get security expression support in annotations. No further plugin configuration is required for this.

That’s fine. What about code?

OK let’s assume we want to build an application where users can manage notes. The domain classes look like this:

class Note {
  String title
  String text
  static belongsTo = [author: User]
}
class User {
  String username
  String password
  static hasMany = [notes: Note]

  static constraints = {
    username blank: false, unique: true
    ...
  }

  // .. rest of generated user class from Spring Security Core plugin 
}

A user can have many notes while a Note has exactly one author. Now we want to create a service which provides some common methods for working with Note objects:

class NoteService {
  public long getTotalNoteCount() { .. }
  public void createNote(Note note) { .. }
  public void updateNote(Note note) { .. }
  public Note getNote(long id) { .. } 
  public void removeNote(long id) { .. }
}

To these service methods we want to apply the following access rules:

  • Everyone should be able to get the total count of notes stored by the system with getTotalNoteCount()
  • Logged in users can create new notes using createNote()
  • Notes can only be read, updated or removed by the author

We start with adding @PreAuthorize to getTotalNoteCount():

@PreAuthorize('permitAll()')
public long getTotalNoteCount() {
  ...
}

@PreAuthorize and @PostAuthorize take a SpEL expression as parameter, which is evaluated to decide if a user is granted access. Everyone should be able to call getTotalNoteCount() so we simply call the predefined permitAll() function.

The security expression for createNote() looks similar:

@PreAuthorize('isFullyAuthenticated()')
public Note createNote(Note note) {
  ...
}

Since only logged in users should be able to create notes we call the isFullyAuthenticated() function within the expression.

So far it would be possible to achieve the same effect using roles. We will see the real bonus of security expressions in the next example. The access rule for updateNote() is slightly more complicated:

@PreAuthorize('isAuthenticated() and principal?.username == #note.author.username')
public Note updateNote(Note note) {
  ...
}

When updating a Note we have to be sure that the logged in user is the author of the Note object he wants to edit. Spring Security populates the SpEL context with a predefined variable named principal which can be used to access the currently logged in user (the list of all predefined variables can be found here). With the # prefix it is possible to access method arguments. So #note in the SpEL expression references the note method parameter. In this example we check if the names of the logged in user (principal) and the author of the passed Note object (#note.author) match. If both are the same the user is allowed to update the Note object.

The getNote() method is a bit different because there is no Note object parameter which can be used to access the author of the note. However, the return value is a Note object which can be checked using @PostAuthorize:

@PostAuthorize('isAuthenticated() and principal.username == returnObject.author.username') 
public Note getNote(long id) {
  ...
}

As mentioned above the @PostAuthorize annotation can be used to evaluate a security expression after a method has been called. Within the @PostAuthorize expression it is possible to access the object returned by the method call using the predefined variable returnObject. Here an AccessDeniedException will be thrown if the logged in user is not the author of the returned Note object.

removeNote() is a bit more complicated. There is no Note object parameter so there is no easy way to validate the note author within @PreAuthorize. @PostAuthorize doesn’t help here either. Even if removeNote()would return the removed Note object @PostAuthorize would check if the user is allowed to remove a Note object after it has been removed. Not that useful…

In the following I will show two different ways of adding the security constraint to removeNote().

1. Using bean references

Within SpEL expressions it is possible to reference beans and delegate the evaluation of security rules to them. This can look like the following piece of code:

@PreAuthorize("@securityService.canRemoveNote(#id)")
public Note removeNote(long id) {
  ...
}

The @ sign is used to reference beans in SpEL expressions. Here, the method canRemoveNote() of a bean named securityService is called. The note ID is passed as parameter to canRemoveNote(). The securityService bean is a standard Grails service that is used to implement the security constraints:

class SecurityService {

  def springSecurityService

  public boolean canRemoveNote(long id) {
    Note note = Note.get(id)
    return note.author == springSecurityService.getCurrentUser()
  }
}

Unfortunately this won’t work out of the box and some small adjustments to the Spring Security configuration need to be done. Some time ago I wrote a short article about exactly this, so I won’t explain it here again. Please have a look at this blog post for more details: Calling bean methods in Spring Security expressions.

2. Using a PermissionEvaluator

An alternative solution for implementing the access rules of removeNote() is to use Spring Security’s build in hasPermission() methods to delegate the security checks to a PermissionEvaluator. Within the security expression context two different hasPermission() methods are available:

hasPermission(Object targetObject, Object permission)
hasPermission(Serializable targetId, String targetType, Object permission)

The first one can be used if an instance of the object that has to be checked is available (like in updateNote(Note note)). The second version can be used of no instance is available and the object needs to be identified by an ID and type. The later one is the one that can help us with the removeNote() method:

@PreAuthorize("hasPermission(#id, 'com.mscharhag.Note', 'remove')")
public Note removeNote(long id) {
  ..
}

Note that the primitive type long of id gets converted to Long which implements the required Serializable interface. Now we have to create our own PermissionEvaluator implementation to define our security constraints. PermissionEvaluator requires the implementation of two methods which directly correlate to the two hasPermission() methods that can be used within security expressions:

boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) 
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission)

The only difference is that Spring Security passes the current authentication state as additional parameter to the PermissionEvaluator. A possible implementation of PermissionEvaluator can look like this:

class GrailsPermissionEvaluator implements PermissionEvaluator {

  def grailsApplication
  def springSecurityService 

  @Override
  public boolean hasPermission(Authentication authentication, Object note, Object permission) {
    def user = springSecurityService.getCurrentUser();
    return permission == 'remove' && note.author == user
  } 

  @Override
  public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
    // get domain class with name targetType
    Class domainClass = grailsApplication.getDomainClass(targetType).clazz

    // get domain object with id targetId
    Note note = domainClass.get(targetId)

    return hasPermission(authentication, note, permission)
  }
}

As we can see the second method resolves targetType and targetId to a Note object, which is then passed to the first method. The first method checks if the currently logged in user is allowed to remove the Note object.

To make this work we need to override the default permissionEvaluator bean that is configured by the Spring Security ACL plugin. This is done in grails-app/conf/spring/resources.groovy :

permissionEvaluator(GrailsPermissionEvaluator) {
  grailsApplication     = ref('grailsApplication')
  springSecurityService = ref('springSecurityService')
}

By default the Grails Spring Security ACL plugin configures an AclPermissionEvaluator instance as permissionEvaluator, which can be used for evaluating ACL rules. However, in this example we are not using ACLs, so we can override it with our own implementation.

In summary bean references and the PermissionEvaluator approach provide both a good way to implement your own security constraints. This is especially useful if your access rules are more complex than the ones in this example.

The final NoteService looks like this:

class NoteService { 

  @PreAuthorize('permitAll()')
  public long getTotalNoteCount() { .. }

  @PreAuthorize('isFullyAuthenticated()')
  public Note createNote(Note note) { .. }

  @PostAuthorize('isAuthenticated() and principal.username == returnObject.author.username') 
  public Note getNote(long id) { .. }

  @PreAuthorize('isAuthenticated() and principal.username == #note.author.username')
  public Note updateNote(Note note) { .. }

  @PreAuthorize("@securityService.canRemoveNote(#id)")
  public Note removeNoteUsingBeanResolver(long id) { .. }

  @PreAuthorize("hasPermission(#id, 'com.mscharhag.Note', 'remove')")
  public Note removeNoteUsingPermissionEvaluator(long id) { .. } 
}
  • The full source code can be found at GitHub.

 

Michael Scharhag

Michael Scharhag is a Java Developer, Blogger and technology enthusiast. Particularly interested in Java related technologies including Java EE, Spring, Groovy and Grails.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button