Custom Permission Evaluator Spring

Thomas Bernhard picture Thomas Bernhard · May 26, 2017 · Viewed 11k times · Source

I want to create a custom Permission Evaluator in order to @PreAuthorize a REST Endpoint with a custom method. I use Spring Boot 1.5.3 with the web and security starter.

My further use case would be to check, if the logged in user is authorized to view the specified id.

Upon calling the REST endpoint I get the following error:

org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method hasPermission(null) cannot be found on org.springframework.security.access.expression.method.MethodSecurityExpressionRoot type

My Custom Permission Evaluator:

@Component
class CustomPermissionsEvaluator implements PermissionEvaluator {

    public boolean hasPermission(String id) {
        return id.equals("correct");
    }

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

My Security Configuration:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends GlobalMethodSecurityConfiguration {

    @Override
    public MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
        methodSecurityExpressionHandler.setPermissionEvaluator(new CompanyPermissionsEvaluator());
        return methodSecurityExpressionHandler;
    }
}

My Rest Controller:

@RestController
class RestControllerToProtect {

    @PreAuthorize("hasPermission(#id)")
    @GetMapping
    public String methodToProtect(String id) {
        return "Authenticated";
    }
}

Stacktrace:

org.springframework.expression.spel.SpelEvaluationException: EL1004E:
Method call: Method hasPermission(null) cannot be found on
org.springframework.security.access.expression.method.MethodSecurityExpressionRoot type

Answer

J-Alex picture J-Alex · May 26, 2017

You can't use overloaded method which is not member of PermissionEvaluator without additional configuration (see this answer if you want to reconfigure PermissionEvaluator pattern).

hasPermission calls should match one of the following signatures by default:

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

hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);

Example:

public class CustomPermissionEvaluator implements PermissionEvaluator {

    private Logger log = LoggerFactory.getLogger(CustomPermissionEvaluator.class);

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
        AbstractEntity abstractEntity = (AbstractEntity) targetDomainObject;
        log.debug("User {} trying to access {}-{} with permission {}",
                customUserDetails.getUsername(),
                abstractEntity.getClass().getSimpleName(),
                abstractEntity.getId(),
                permission.toString());
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
        log.debug("User {} trying to access {}-{} with permission {}",
                customUserDetails.getUsername(),
                targetType,
                targetId,
                permission.toString());
        return false;
    }
}

Controller:

@RestController
public class RestControllerToProtect {
    // passing targetDomainObject and permission, authentication is detected by SecurityExpressionRoot
    @PreAuthorize("hasPermission(#abstractEntity, 'create')")
    public String methodToProtect(@RequestBody AbstractEntity abstractEntity) {
        return "Authenticated";
    }
}