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
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";
}
}