Move all your validations out of your business logic.
<dependency>
<groupId>io.github.encryptorcode</groupId>
<artifactId>spring-permissions</artifactId>
<version>1.0.0</version>
</dependency>
implementation 'io.github.encryptorcode:spring-permissions:1.0.0'
implementation("io.github.encryptorcode:spring-permissions:1.0.0")
Have you ever wanted a way to separate huge piles of permission checks and validations out of your business layer ? Have you ever felt your method sizes are way too big, but you weren't able to do anything about it ? For the answer both the above, Spring Permissions is a layer that will help you split your services into validation and actual logic by using simple annotations.
@Controller
class MyController{
@RequestMapping(path = "/myapi")
@Permission(id = "my.permission.id")
@ResponseBody
public String myApi(){
return "You are valid user.";
}
}
Adding permissions to a controller is as easy adding an annotation to the controller method. Just add the @Permission annotation with the right permission id and permission handler method will be automatically invoked.
import io.github.encryptorcode.permissions.abstracts.PermissionHandler;
import io.github.encryptorcode.permissions.annotations.Handler;
class MyPermissionHandler extends PermissionHandler {
public MyPermissionHandler(Variables variables){
super(variables);
}
@Handler(id = "my.permission.id")
public void myPermissionValidator() throws Exception {
if(some condition){
throw new Exception("Seems like you don't have permission to perform this operation");
}
}
}
You need to follow certain rules when writing a permission handler
- You can define any number of classes and methods.
- All the classes should extend
PermissionHandler
class - All the classes should have a constructor with just
Variables
param - All the methods should be annotated with
@Handler
and permission id should be specified - All the methods should be of public visibility and should return void
PermissionManager.init("your.app.package.name");
Make sure you make this call immediately after your server starts.
In case of tomcat you will want to register a listener and invoke this once the server is started.
import io.github.encryptorcode.permissions.service.PermissionInterceptor;
import io.github.encryptorcode.permissions.service.VariablesResolver;
@EnableWebMvc
@ComponentScan(basePackages = {"your.app.package.name"})
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getPermissionInterceptor());
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(getPermissionVariableResolver());
}
@Bean
public PermissionInterceptor getPermissionInterceptor() {
return new PermissionInterceptor();
}
@Bean
public VariablesResolver getPermissionVariableResolver() {
return new VariablesResolver();
}
}
We need to register both PermissionInterceptor
and VariablesResolver
to spring.
If you are using springs with annotation based configurations you add those in your WebMvcConfigurer class as given in the above snippet.
In permission handler you can use the variables object provided from the constructor.
Say for example you need to store the result of a db operation dao.get("data")
and pass the result of this to the controller, you can do the same as below.
data = variables.pipe("name", () -> dao.get("data"));
Then in the controller method you can define an argument as below. This will automatically get the data you set using variables.pipe()
in the permissions layer.
@HandlerVariable("name") String data
You need to pass the key name and a lambda expression that returns the retrieved data.
lambda expression will be automatically invoked and the result will be stored in the specified key and returned.
If this method is invoked with the same key again lambda expression WILL NOT be evaluated, so the existing data will be returned.
If you need to forcefully execute the lambda function you may also invoke pipeNew(key, lambda)
for the same.
You may define arguments for your permission handler methods and you have to set argument values in args param of @Permission annotation.
Say for example, you need to pass 2 arguments the word id
and request param value for id
to permissions handler. You can do as follows.
In controller
@Permission(id = "my.permission", args = {"id", "${param.id}"})
In permission handler
@Handler(id = "my.permission")
public void myPermission(String id, String idParam) throw Exception{
// your logic
}
${path.name}
→ Path value taken from the url${query.name}
or${param.name}
→ Request param value${header.name}
→ Header value${cookie.name}
→ Cookie value
We have made an example application for demo purposes, Feel free to clone this repository and run mvn clean package cargo:run
inside the example folder.
A few interesting files you might like to refer from the example folder: