javaspring-bootspring-securityauthorizationshiro

Achieve Dynamic REST Resource specific Authorization


We have a SpringBoot based module, we have REST APIs which allow creating resources with Params like

Request

POST /resources

{
  "resourceName": "Res1",
  "admins": ["john.doe@company.com", "jane.doe@company.com"]
}

Response

{
  "id": "R1"
  "resourceName": "Res1",
  "admins": ["john.doe@company.com", "jane.doe@company.com"]
}

Request

POST /resources

{
  "resourceName": "Res2",
  "admins": ["alice@company.com", "bob@company.com"]
}

Response

{
  "id": "R2"
  "resourceName": "Res2",
  "admins": ["alice@company.com", "bob@company.com"]
}

For R1 update API should only be accesible by John/Jane

Request

PUT /resources/R1

{
  "resourceName": "Resource1",
  "admins": ["john.doe@company.com", "jane.doe@company.com", "jacob@company.com"]
}

Response

For John / Jane the response should be:

{
  "id": "R1"
  "resourceName": "Resource1",
  "admins": ["john.doe@company.com", "jane.doe@company.com", "jacob@company.com"]
}

When Alice / Bob user are updating R1 this response should be 403 Forbidden

Similarly For R2 update API should only be accesible by Alice / Bob.

When John / Jane are updating R2 this response should be 403 Forbidden

Please suggest which framework can be used to achieve this, preferably with less boiler plate

Currently we have a system where resource access is in for of RoleBasedAccessControl. We achieve restriction by storing permissions. The RBAC config is saved in DB.

But now we need more fine grained control per resource which can be managed directly by existing admins


Solution

  • This can be achieved using a method level security from SpringBoot.

    First enable Method Level Security by annotating a Config class like -

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class GlobalMethodConfig extends GlobalMethodSecurityConfiguration
    

    Then provide an implementation of PermissionEvaluator class like -

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, Object permission) {
      if (permission.equals("RESOURCE_WRITE_AUTHORITY")) {
        Resource res = resourceRepository.getResourceById((String) targetId);
        if(env != null) {
          return env.getAdmins().contains(authentication.getName());
        }
      }
      return false;
    }
    

    After which you can annotate your PUT method in controller or the corresponding method in the service, to use PermissionEvaluator, like

    @PreAuthorize("hasPermission(#resourceName, 'RESOURCE_WRITE_AUTHORITY')")
    public Environment updateResource(String resourceName)
    

    References - https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#:~:text=15.3.1%C2%A0%40Pre%20and%20%40Post%20Annotations