Spring MVC (RESTful API): checking payload against path variable

Usage example:

  • let the RESTful project create an operation using the POST HTTP protocol - creating tickets, where the creator (assignor) indicates the ticket holder
  • we create a new “ticket” in the following location: /companyId/userId/ticket
  • we provide a ticket body containing assigneeId :

    {"assigneeId": 10}

  • we need to verify that assigneeId belongs to the company in the URL - companyId path variable

Until:

 @RequestMapping(value="/{companyId}/{userId}/ticket", method=POST) public void createTicket(@Valid @RequestBody Ticket newTicket, @PathVariable Long companyId, @PathVariable Long userId) { ... } 
  • we can easily specify a custom Validator ( TicketValidator ) (even with dependencies) and check the Ticket instance
  • we cannot easily pass companyId to this validator! We need to verify that ticket.assigneeId owned by a company with companyId .

Required Conclusion:

  • the ability to access path variables in custom validators

Any ideas how I can achieve the desired result here?

+6
source share
2 answers

You can always do this:

 @Controller public class MyController { @Autowired private TicketValidator ticketValidator; @RequestMapping(value="/{companyId}/{userId}/ticket", method=POST) public void createTicket(@RequestBody Ticket newTicket, @PathVariable Long companyId, @PathVariable Long userId) { ticketValidator.validate(newTicket, companyId, userId); // do whatever } } 

Edit in response to comment:

It does not make sense to check the Ticket regardless of companyId when the fairness of the Ticket depends on the companyId .

If you cannot use the solution above, consider grouping Ticket with companyId into DTO and change the mapping as follows:

 @Controller public class MyController { @RequestMapping(value="/{userId}/ticket", method=POST) public void createTicket(@Valid @RequestBody TicketDTO ticketDto, @PathVariable Long userId) { // do whatever } } public class TicketDTO { private Ticket ticket; private Long companyId; // setters & getters } 
+1
source

If we assume that our custom validator knows the name of the required property, we can do something like this:

Approach one:

1) We can move this logic of path variables to some basic validator:

 public abstract class BaseValidator implements Validator { @Override public boolean supports(Class<?> clazz) { // supports logic } @Override public void validate(Object target, Errors errors) { // some base validation logic or empty if there isn't any } protected String getPathVariable(String name) { // Getting current request (Can be autowired - depends on your implementation) HttpServletRequest req = HttpServletRequest((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); if (req != null) { // getting variables map from current request Map<String, String> variables = req.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); return variables.get(name); } return null; } } 

2) Extend it with the implementation of TicketValidator :

 public class TicketValidator extends BaseValidator { @Override public void validate(Object target, Errors errors) { // Getting our companyId var String companyId = getPathVariable("companyId"); ... // proceed with your validation logic. Note, that all path variables // is `String`, so you're going to have to cast them (you can do // this in `BaseValidator` though, by passing `Class` to which you // want to cast it as a method param). You can also get `null` from // `getPathVariable` method - you might want to handle it too somehow } } 

Approach to two:

I think it's worth mentioning that you can use the @PreAuthorize annotation with SpEL to perform such a check (you can pass path variables and query the body). You will get an HTTP 403 code, though if the woudnt check passes, so I think that doesn’t mean what you want.

+1
source

All Articles