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!
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