1

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!

batero
  • 113
  • 2
  • 10
  • 2
    See suggestion here: https://stackoverflow.com/a/37060389/1356423. You can apply security at the method level so the queries do not change. Every entity associated with a user implements a common interface returning the User. User is compared against user in security context. – Alan Hay Sep 25 '19 at 12:40
  • Thank you @alan-hay. I think I got the idea but I still have some doubts about it. If I'm right, I need to get the result first in order to post check the owner against the user who did the request. If meet the conditions, then return the results, or empty in other case. However, with this approach I'm still performing a full call to the database for finally dump the results if they don't meet the previous conditions. Are there other solutions where avoiding a previously call to the database to meet the conditions or adding the conditions to the query? – batero Sep 25 '19 at 15:39
  • How else could you know without going to the db? A call to the db takes milliseconds. Additionally it will only be a scenario that occurs with malicious use. – Alan Hay Sep 25 '19 at 19:16
  • After several tries I just finished to test your workaround whereas I added a small change in order to suit it better to my project necessities. Thank you for your POV, which help me to find out how to proceed – batero Sep 30 '19 at 16:48

1 Answers1

0

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

batero
  • 113
  • 2
  • 10