0

I have a REST controller with a GET method. It returns a resource. I want to verify if the resource belongs to the authorized user by comparing the owner field on the Resource with the authorized user's login. With a normal synchronous request I'd do something like this:

@RestController
@RequestMapping("/api")
public class AController {

    private final AService aService;

    public AController(AService aService) {
        this.aService = aService;
    }

    @GetMapping("/resources/{id}")
    @PostAuthorize("returnObject.ownerLogin == authentication.name")
    public Resource getResource(@PathVariable Long id) {
        return aService.getResource(id);
    }
}

But what if the controller method is asynchronous (implemented with DeferredResult)?

@RestController
@RequestMapping("/api")
public class AController {

    private final AService aService;

    public AController(AService aService) {
        this.aService = aService;
    }

    @GetMapping("/resources/{id}")
    @PostAuthorize("returnObject.ownerLogin == authentication.name")
    public DeferredResult<Resource> getResource(@PathVariable Long id) {
        DeferredResult<Resource> deferredResult = new DeferredResult<>();

        aService
            .getResourceAsync(id)
            .thenAccept(resource -> {
                deferredResult.setResult(resource);
            });

        return deferredResult;
    }
}

Where AService interface looks like this:

@Service
public class AService {

    @Async
    public CompletableFuture<Resource> getResourceAsync(Long id) {
        // implementation...
    }

    public Resource getResource(Long id) {
        // implementation...
    }
}

And Resource class is a simple DTO:

public class Resource {

    private String ownerLogin;

    // other fields, getters, setters

}

In the second example Spring Security obiously looks for the ownerLogin field on the DeferredResult instance. I'd like it to treat the asynchronously resolved Resource as the returnObject in the @PostAuthorize SPEL expression.

Is it possible? Maybe someone can suggest an alternatve approach? Any suggestions are welcome.

jannis
  • 4,843
  • 1
  • 23
  • 53

1 Answers1

0

Couldn't achieve my goal with PostAuthorize and endedd up doing the following:

Made Resource a subresource of the User resource. Used a PreAuthorize annotation to validate user's login.

@RestController
@RequestMapping("/api")
public class AController {

    private final AService aService;

    public AController(AService aService) {
        this.aService = aService;
    }

    @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}/resources/{id}")
    @PreAuthorize("#login == authentication.name")
    public DeferredResult<Resource> getResource(@PathVariable String login, @PathVariable Long id) {
        DeferredResult<Resource> deferredResult = new DeferredResult<>();

        aService
            .getResourceAsync(login, id)
            .thenAccept(resource -> {
                deferredResult.setResult(resource);
            });

        return deferredResult;
    }
}

Added an ownership check in AService. If Resource owner and the requesting user's login don't match throw an Exception that resolves to a 404 HTTP status:

@Service
public class AService {

    private final ARepository aRepository;

    public AController(ARepository aRepository) {
        this.aRepository = aRepository;
    }

    @Async
    public CompletableFuture<Resource> getResourceAsync(String owner, Long id) {
        Resource resource = aRepository.getResource(id);

        if (!resource.owner.equals(owner)) {
            // resolves to 404 response code
            throw ResourceNotFounException();
        }

        return resource;
    }
}
jannis
  • 4,843
  • 1
  • 23
  • 53