Spring in Practice

Willie Wheeler's Spring blog

Quick Tip: Spring Security Role-based Authorization and Permissions

| Comments

The problem: hardcoded role-based authorization

One of the challenges around using Spring Security is that the examples—both in the documentation and on the web—tend to promote an overly-simple approach to role-based authorization, hardcoding roles in the source in a non-configurable fashion. For example:

public Newsletter getFacultyNews() { ... }

(Assume for the sake of example that ACL-based authorization is overkill for the method in question. The user either has permission to read faculty newsletters or not.)

The problem is that when we decide to make a change—for example, maybe teaching assistants should be allowed to read the faculty newsletters too—we have to go into the code to make a change:

@PreAuthorize("hasRole('facultyMember') or hasRole('teachingAssistant')")
public Newsletter getFacultyNews() { ... }

For domain object security there’s no problem because the permissions are cleanly separated from roles. We can map associate individual permissions on domain objects with users and roles as we wish. So the code contains annotations like

@PreAuthorize("hasPermission(#message, write)")
public void editMessage(Message message) { ... }

and all is good. We probably won’t need to change the relationship between the permission and the method itself; we’ll only need to change who (which users/roles) actually has the write permission on the message in question, and we can do that in the database. So that is nice, and we want the same thing for role-based authorization.

Solution: use granted authorities to model permissions, not roles

Here we assume an authentication source that models the desired relationship between users, roles and permissions. The typical relationship would be a many-many relationship between users and roles, and a many-many relationship between roles and permissions. For example:

Sample custom user schema

It would be possible to have a direct relationship between users and permissions too (say to allow for the assignment of fine-grained permissions to specific users in addition to assigning roles), if that were desired.

The schema can be part of some standard authentication source or it can be a custom UserDetailsService; it doesn’t matter.

At the end of the day we need to transform our user representation into a UserDetails, and the trick is to map permissions—not roles—to GrantedAuthority objects to support the getAuthorities() contract on the UserDetails interface. We still have roles, but they matter only insofar as they help to bundle permissions up into convenient packages. The UserDetails implementation will probably expose the roles, but the UserDetails interface simply exposes the permissions (not the roles) via the getAuthorities() method.

It’s really that simple, and the final result is that we can avoid hardcoding roles in the code:

public Newsletter getFacultyNews() { ... }

As an aside, the predicate name hasRole rather than hasAuthority is a minor annoyance since permissions aren’t roles. The backing check is against a GrantedAuthority and so hasRole() seems to reflect either the intended or the typical use of GrantedAuthority.