【问题标题】:How to wrap JSON response from Spring REST repository?如何包装来自 Spring REST 存储库的 JSON 响应?
【发布时间】:2016-10-29 20:18:42
【问题描述】:

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

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

带有相应获取请求方法的 REST 控制器:

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

现在纪念品类是一个pojo:

@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#Always_return_JSON_with_an_Object_on_the_outsidehttp://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 控制器,它就可以将这些对象包装在一个对象包装器中,这样就不会直接序列化对象列表?

【问题讨论】:

  • 春季替代品?你在说什么? Struts 有,Spring 没有。详细说明你的问题。只有包装器是 Spring 对你的帽子,或者如果它们不是结局,你可以即时扩展消息。
  • 给你:docs.spring.io/spring/docs/current/javadoc-api/org/…。在您自己的实现中,您可以将其包装成您需要的任何结构。
  • 多么有趣的问题.. 如果实际值是可迭代的,您可以尝试使用@ControllerAdvice 返回您的通用包装器,或者您可以尝试劫持反序列化器,在您的情况下可能是 Jackson Http 消息转换器。让我们发布:)

标签: java json spring rest spring-restcontroller


【解决方案1】:

我最终得到了以下解决方案(感谢@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&lt;Souvenir&gt; souvenirs(@PathVariable("user") String user)) 中,并且未来的控制器不必担心将其 Iterables 包装在这样的包装器中,因为框架会完成这部分工作。

【讨论】:

  • 很高兴它对您有所帮助。这里需要注意的一件事:出于安全原因,这是可以的,但如果您要手动为集合创建包装器并且例如提供集合的大小,它可能会非常有用。 {size:10, fortunes:[..]} 手动创建包装对象时还有其他可能性。
猜你喜欢
  • 2017-01-07
  • 2014-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
  • 1970-01-01
  • 2017-12-27
相关资源
最近更新 更多