Securing a JSON API REST Service With Spring Boot and Elide
Learn how to make use of the security layer in Elide to make some decisions about who can perform what operation.
Join the DZone community and get the full member experience.
Join For FreeIn a previous article we saw how to create new entities and how to set the relationships between entities by implementing support for Elide’s post() method. But security restrictions prevented us from actually modifying these relationships.
Now that our API has authentication, we can make use of the security layer in Elide to make some decisions about who can perform what operation.
A quick note here that this article is based on Elide 2.0.1, which fixes up some bugs from the version 1 we were using in previous articles. Specifically, you may have noticed that we were getting security errors in previous articles despite setting the security mode to inactive. This issue is now resolved in Elide 2.
Before we can make use of the security features in Elide, we need to enable security for our endpoints. This is done by passing the SecurityMode.SECURITY_ACTIVE value to the Elide methods get() and post().
So let’s take a look at what “allow all” permissions look like with Elide:
package com.matthewcasperson.elidetest.jpa;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.yahoo.elide.annotation.*;
import com.yahoo.elide.security.checks.prefab.Role;
import java.io.Serializable;
import javax.persistence.*;
import java.util.List;
/**
* The persistent class for the parent database table.
*
*/
@ReadPermission(any={Role.ALL.class })
@SharePermission(any={Role.ALL.class })
@CreatePermission(any={Role.ALL.class })
@UpdatePermission(any={Role.ALL.class })
@DeletePermission(any={Role.ALL.class })
@Entity
@Table(name="parent")
@NamedQuery(name="Parent.findAll", query="SELECT p FROM Parent p")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String description;
private String name;
private List<Child> children;
public Parent() {
}
@Id
@Column(unique=true, nullable=false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
@Column(length=45)
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
@Column(length=45)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
//bi-directional many-to-one association to Child
@OneToMany(mappedBy="parent")
@JsonManagedReference
public List<Child> getChildren() {
return this.children;
}
public void setChildren(List<Child> childs) {
this.children = childs;
}
public Child addChild(Child child) {
getChildren().add(child);
child.setParent(this);
return child;
}
public Child removeChild(Child child) {
getChildren().remove(child);
child.setParent(null);
return child;
}
}
Permissions in Elide are assigned through the annotations @CreatePermission, @ReadPermission, @UpdatePermission and @DeletePermission, which map to the common CRUD operations that you’ll perform when interacting with any database. These annotations are assigned to the JPA entities. In addition to these four CRUD permission annotations, there is also a @SharePermission annotation which is used to determine if entities can be related to one another.
All these permission annotations take an array of classes assigned to the any or all parameters. As you would expect, all checks assigned to the all parameter must pass for the operation to succeed, while one or more of the checks assigned to the any parameter must pass for the operation to succeed.
The Role.ALL.class is provided by the Elide library to always permit the operation.
With the five annotations above applied to each of the JPA entities, and with each annotation having a Role.ALL.class assigned to the any parameter, we are now free to perform any action supported by the Elide library. Now, when we POST
{
"data": { "type": "child", "id": "1" }
}
to http://localhost:8080/parent/2/relationships/children, we see that the child with the ID of 1 is assigned to the parent with the ID of 2.
If you want to achieve the reverse, and deny anyone from doing anything with the API, you can use the Role.NONE.class.
@ReadPermission(any={Role.NONE.class })
@SharePermission(any={Role.NONE.class })
@CreatePermission(any={Role.NONE.class })
@UpdatePermission(any={Role.NONE.class })
@DeletePermission(any={Role.NONE.class })
Now, the same POST operation results in the following response:
{
"errors": [
"ForbiddenAccessException"
]
}
Obviously a blanket “deny all” security policy is quite useless in real life, but by mixing and matching the Role.ALL.class and Role.NONE.class with the various operations provided by Elide, it is possible to customize the access to your entities via your API.
In the next article, we’ll look at creating customized permission checks to provide even more fine-grained access based on user roles.
Download the source code for this article from here.
Opinions expressed by DZone contributors are their own.
Comments