如何包装来自Spring REST存储库的JSON响应? [英] How to wrap JSON response from Spring REST repository?

查看:138
本文介绍了如何包装来自Spring REST存储库的JSON响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Spring REST控制器,它返回以下JSON负载:

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

REST控制器及其相应的get请求方法:

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

现在,纪念品课是一场礼拜活动:

@Entity
@Data
public class Souvenir {

    @Id
    @GeneratedValue
    private long id;

    private String title;

    private Date date;
}

关于> https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines_Alwith_return_Object http://haacked.com/archive/2009/06/25/json-hijacking.aspx/我想将响应包装在一个对象中,这样它就不会受到攻击.我当然可以做这样的事情:

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

这将导致以下JSON负载:

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

这有助于防止某些JSON/Javascript攻击,但我不喜欢Wrapper类的冗长性.我当然可以使用泛型来概括上述方法.在Spring生态系统中是否有另一种方法可以达到相同的结果(带有注释或类似内容)?一个想法是该行为是由Spring自动完成的,因此只要有一个REST控制器返回对象列表,它就可以将这些对象包装在对象包装器中,这样就不会序列化任何直接的对象列表?

解决方案

我最终得到了以下解决方案(感谢@ vadim-kirilchuk):

我的控制器看上去仍然和以前一样:

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

我添加了ResponseBodyAdvice的以下实现,该实现基本上在引用包中的控制器尝试响应客户端调用时执行(据我所知):

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

因此,通过这种方法,我可以将现有的方法签名保留在控制器(public Iterable<Souvenir> souvenirs(@PathVariable("user") String user))中,以后的控制器不必担心将其Iterables包装在这样的包装器中,因为框架完成了这部分工作./p>

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?

解决方案

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.

这篇关于如何包装来自Spring REST存储库的JSON响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆