2

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
  • 143
  • 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

2 Answers2

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
  • 143
  • 2
  • 10
0

If the request contains identifying info (like a username) then this can be solved in a simpler way. See this stackoverflow answer. I pasted the info and docs below.

You can also accomplish this using Spring Security's @PreAuthorize which supports expressions:

@PreAuthorize("#userId == principal.id")
public void doSomething(@PathVariable String userId);

See the Spring docs:

Access Control using @PreAuthorize and @PostAuthorize

If Spring Data’s @Param annotation is present on at least one parameter for the method, the value will be used. This is useful for interfaces compiled with a JDK prior to JDK 8 which do not contain any information about the parameter names. For example:

@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);

Behind the scenes this use implemented using AnnotationParameterNameDiscoverer which can be customized to support the value attribute of any specified annotation.

Any Spring-EL functionality is available within the expression, so you can also access properties on the arguments. For example, if you wanted a particular method to only allow access to a user whose username matched that of the contact, you could write

@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

Here we are accessing another built-in expression, authentication, which is the Authentication stored in the security context. You can also access its "principal" property directly, using the expression principal. The value will often be a UserDetails instance, so you might use an expression like principal.username or principal.enabled.

Avery Sturzl
  • 82
  • 1
  • 13