Custom Security With a Spring Boot/Elide Json API Server
In the last article, we looked at some simple security permissions that could be applied to JPA entities to allow or prevent the reading, creation, updating, deletion, and assignment of entities through relationships.
The built in Role.ALL and Role.NONE classes are useful for coarse-grained authorization but offer no ability to distinguish between users. A more likely scenario is one where privileged users can perform operations like create, delete, update and share, while all users can read entities.
The groundwork for providing different levels of access based on the user was laid down when we added authentication to the API. This provided us with a Principal object that has been passed into Elide’s various methods like get() and post().
In order to make use of this Principal to permit or deny actions based on who is logged in, we need to create a custom instance of the Elide UserCheck class.
package com.matthewcasperson.elidetest.permissions;
import com.yahoo.elide.security.User;
import com.yahoo.elide.security.checks.UserCheck;
import org.springframework.security.authentication.AbstractAuthenticationToken;
/**
* A security check that passes for admin users
*/
public class AdminCheck extends UserCheck {
@Override
public boolean ok(User user) {
final AbstractAuthenticationToken springUser = (AbstractAuthenticationToken) user.getOpaqueUser();
return springUser.getAuthorities().stream()
.anyMatch(a -> "ROLE_ADMIN".equalsIgnoreCase(a.getAuthority()));
}
}
The AdminCheck class gets access to the opaque user object, which is the Principal that Spring injected into our REST API methods and we passed into the Elide get() and post() methods.
This opaque user object is then cast to a Spring AbstractAuthenticationToken object. AbstractAuthenticationToken implements the Java Principal interface, but exposes some additional information, like what roles have been assigned to the user via the getAuthorities() method. Converting this collection to a Java 8 stream and finding an admin role allows us to return true for admin users, and false for everyone else. If we return true, the security check passes, and the operation is allowed to succeed. Return false, and the operation will be blocked.
In order to give admin users full access to our API, while providing everyone else with read-only access, we need to assign our new class to the @SharePermission, @CreatePermission, @UpdatePermission and @DeletePermission annotations on our JPA entities.
The @ReadPermission annotation will still continue to reference the Role.ALL class.
@ReadPermission(any={ Role.ALL.class })
@SharePermission(any={ AdminCheck.class })
@CreatePermission(any={ AdminCheck.class })
@UpdatePermission(any={ AdminCheck.class })
@DeletePermission(any={ AdminCheck.class })
With this code in place, all users can read entities, but only admin users can make modifications.
Download the source code for this project from GitHub.