Search code examples
restapispring-bootspring-securityjwt

rest - approaches to restrict a user to their own data


I'm facing a difficulty where I need to restrict the scope of a request to a specific user's data only. Having an API where a previous authenticated user can retrieve a data list, I first need to validate if the requested data is owned by the requester.

For example, a user with id 1 could only retrieve a subset of data where the owner is, in fact, the one with id 1. Calling to /api/elements/{elementId} with a previous authenticated user should only return a element with the provided elementId IF the user who did the request is its owner or IF the user who did the request has an ADMIN role (or any other granted role). In any other case should return null or a 404 HTTP status.

After researching for a while, I've got this approach:

Use the user ID in every CRUD operation: this approach forces to check if the current element is owned by the user who did the request.

Pros: I can assure that only the data owned by that user will be read/written

Cons: I need to add the user id in ALL the queries and also restricting the data returned, excluding from this condition a user with role ADMIN who can query everything without any restriction.

I'm using Spring Boot with Spring Security and JWT for authentication/authorization. Everything works fine, but having this restriction have made me think in some sort of Framework solution (if possible) instead the approach commented above.

How would you approach this problem to solve this restriction with these conditions?

Links related to this issue but with a no specific solution to my case:

Thank you in advance!


Solution

  • After several tries and also with some @alan-hay help, I decided on a workaround where, before access to a specific resource, check if the user (who is already authenticated) has enough privileges to access to that resource.

    So I created a service class named AuthorizationService like this:

    @Component
    public class AuthorizationService {
        @Autowired
        private AuthenticationUser authenticationUser;
    
        public boolean hasAccess(Long elementId, AccessValidatorBo accessValidatorBo) {
            return accessValidatorBo.hasAccess(elementId, authenticationUser.getAuthenticatedUser());
        }
    }
    

    Where elementId is the ID of the element tried to be accessed, accessValidatorBo the wrapper of the Business Object where the logic is executed to assure if the current user has access to the resource mentioned earlier and authenticationUser which is the Spring security authenticated user.

    So, in the controller, I added a @PreAuthorize annotation in order to validate first if the user requesting access to the resource is allowed to do it.

    @RestController
    @RequestMapping(path = ScriptUrlDefinition.BASE)
    public class ScriptController {
        @Autowired
        private ScriptBo scriptBo;
        @Autowired
        private AuthenticationUser authenticationUser;
    
        @GetMapping(path = ScriptUrlDefinition.GET)
        @PreAuthorize("(" + ServiceSecurityConstants.HAS_ROLE_ADMIN + " or " + ServiceSecurityConstants.HAS_ROLE_STANDARD + ") "
                + "and @authorizationService.hasAccess(#scriptId, @scriptBoImpl)")
        @ResponseBody
        public ViewScript get(@PathVariable(name = ScriptUrlDefinition.PATH_VARIABLE_ID) Long scriptId) {
            return scriptBo.getScript(scriptId);
        }
    }
    

    If the user has access, then the service returns the response; otherwise the @PreAuthorize annotation throws a org.springframework.security.access.AccessDeniedException exception