Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap JSON response from Spring REST repository?

I have a spring REST controller which returns the following JSON payload:

[
  {
    "id": 5920,
    "title": "a title"
  },
  {
    "id": 5926,
    "title": "another title",
  }
]

The REST controller with its corresponding get request method:

@RequestMapping(value = "example")
public Iterable<Souvenir> souvenirs(@PathVariable("user") String user) {
    return new souvenirRepository.findByUserUsernameOrderById(user);
}

Now the Souvenir class is a pojo:

@Entity
@Data
public class Souvenir {

    @Id
    @GeneratedValue
    private long id;

    private String title;

    private Date date;
}

Regarding https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside and http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ I would like to wrap the response within an object so that it is not vulnerable to attacks. Of course I could do something like this:

@RequestMapping(value = "example")
public SouvenirWrapper souvenirs(@PathVariable("user") String user) {
    return new SouvenirWrapper(souvenirRepository.findByUserUsernameOrderById(user));
}

@Data
class SouvenirWrapper {
  private final List<Souvenir> souvenirs;

  public SouvenirWrapper(List<Souvenir> souvenirs) {
    this.souvenirs = souvenirs;
  }
}

This results in the following JSON payload:

   {
     "souvenirs": [
        {
          "id": 5920,
          "title": "a title"
        },
        {
          "id": 5926,
          "title": "another title",
        }
    ]
  }

This helps in preventing some JSON/Javascript attacks but I don't like the verbosity of the Wrapper class. I could of course generalize the above approach with generics. Is there another way to achieve the same result in the Spring ecosystem (with an annotation or something similar)? An idea would be that the behaviour is done by Spring automatically, so whenever there is a REST controller that returns a list of objects, it could wrap those objects within an object wrapper so that no direct list of objects get serialized?

like image 242
Mirco Widmer Avatar asked Oct 29 '16 20:10

Mirco Widmer


People also ask

How pass JSON object in post request in spring boot?

Send JSON Data in POST Spring provides a straightforward way to send JSON data via POST requests. The built-in @RequestBody annotation can automatically deserialize the JSON data encapsulated in the request body into a particular model object. In general, we don't have to parse the request body ourselves.

What is @RestController annotation in spring boot?

Spring RestController annotation is a convenience annotation that is itself annotated with @Controller and @ResponseBody . This annotation is applied to a class to mark it as a request handler. Spring RestController annotation is used to create RESTful web services using Spring MVC.

How do I create a custom response in spring boot?

Steps. Please create a new package with the name 'response' in which we will create a class with the name “ResponseHandler”. This class will later be used to generate responses, where the response will be received in the form of an object with 3 parameters/values ​​in it.


1 Answers

I ended up with the following solution (thanks to @vadim-kirilchuk):

My controller still looks exactly as before:

@RequestMapping(value = "example")
public Iterable<Souvenir> souvenirs(@PathVariable("user") String user) {
    return new souvenirRepository.findByUserUsernameOrderById(user);
}

I added the following implementation of ResponseBodyAdvice which basically gets executed when a controller in the referenced package tries to respond to a client call (to my understanding):

@ControllerAdvice(basePackages = "package.where.all.my.controllers.are")
public class JSONResponseWrapper implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof List) {
            return new Wrapper<>((List<Object>) body);
        }
        return body;
    }

    @Data // just the lombok annotation which provides getter and setter
    private class Wrapper<T> {
        private final List<T> list;

        public Wrapper(List<T> list) {
            this.list = list;
        }
    }
}

So with this approach I can keep my existing method signature in my controller (public Iterable<Souvenir> souvenirs(@PathVariable("user") String user)) and future controllers don't have to worry about wrapping its Iterables within such a wrapper because the framework does this part of the work.

like image 141
Mirco Widmer Avatar answered Oct 31 '22 16:10

Mirco Widmer