Spring Security custom PermissionEvaluator

Viewed 4123

I'm using JWT token authorization. I'm trying to restrict access to some of REST api endpoints. I would like it to work like that: code below should be only executed when authenticated user id == id given in @PathVariable

@PreAuthorize("hasAnyAuthority('ADMIN', 'USER')")
@GetMapping(value = "orders/{id}", produces = "application/json")
public EntityModel<Order> getOrders(@PathVariable Long id) { ... )

These articles more or less describe what I trying to acomplish: similar post, article

I don't understand how second link CustomPermissionEvaluator implements PermissionEvaluator should look like in my case. I would be grateful if someone would give me some tips.

My second concern is that all I can do later on, is to get username authentication.getName(), and then i have to use UserRepository to findUserByUsername(String username). Is it normal to do it this way? That's additional database query. I wonder if it is possible to somehow add userID to token (because function that returns UsernamePasswordAuthenticationToken already is getting user by username anyway).

1 Answers

To trigger PermissionEvaluator , you have to use hasPermission() in @PreAuthorize. There are 2 versions of hasPermission() which are :

(1) @PreAuthorize("hasPermission('foo' ,'bar')") which will call

boolean hasPermission(Authentication authentication, Object targetDomainObject,Object permission);

/** targetDomainObject = 'foo',  permission = 'bar' **/

(2) @PreAuthorize("hasPermission('foo' ,'bar','baz')") which will call

boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);
/** targetId = 'foo' , targetType = 'bar' , permission = 'baz' **/

In both cases , the Authentication parameter is the Authentication token get from the SecurityContext.

One thing to note is that when configuring @PreAuthorize("hasPermission()"), you can use #foo , @P or @Param from spring data to specify which argument in the protected method will be used to invoke the PermissionEvaluator . See this for more details.

In your case , you could do something like:

@PreAuthorize("hasPermission('#id', 'getOrder')")
public EntityModel<Order> getOrders(@PathVariable Long id) {

}

and the PermissionEvaluator looks like :

public class MyPermissionEvaluator implements PermissionEvaluator {


    @Override
    public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {

            MyAuthentication myAuth = (MyAuthentication) auth;
            Long targetId =  (Long) id;
            String permssionStr = (String) permission;

            if(permssionStr.equals("getOrder")){
                return myAuth.getUserId().equals(targetId);
            }else if(permssionStr.equals("xxxx"){
                //other permission checking
            }
    }
}

Please note that it assumes you also customize the Authentication token to be MyAuthentication which includes userID. It also answers your 2nd concern is that you can customize the authentication process to return a customzied Authentication token which you can set the userId into it just after loading the user record for authentication. In this way , the userId will be stored inside the MyAuthentication which you do not need to query it again in the PermissionEvaluator.

Alternatively , you can also consider to directly express the authorisation logic in the @PreAuthorize without using hasPermission() for such simple case:

@PreAuthorize("#id == authentication.userId")
public EntityModel<Order> getOrders(@PathVariable Long id) {

}
Related