如何从Spring REST存储库包装JSON响应?

Mir*_*mer 5 java rest spring json spring-restcontroller

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

[
  {
    "id": 5920,
    "title": "a title"
  },
  {
    "id": 5926,
    "title": "another title",
  }
]
Run Code Online (Sandbox Code Playgroud)

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

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

现在,纪念品课是一场pojo:

@Entity
@Data
public class Souvenir {

    @Id
    @GeneratedValue
    private long id;

    private String title;

    private Date date;
}
Run Code Online (Sandbox Code Playgroud)

关于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;
  }
}
Run Code Online (Sandbox Code Playgroud)

这将导致以下JSON有效负载:

   {
     "souvenirs": [
        {
          "id": 5920,
          "title": "a title"
        },
        {
          "id": 5926,
          "title": "another title",
        }
    ]
  }
Run Code Online (Sandbox Code Playgroud)

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

Mir*_*mer 10

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

我的控制器看起来仍然和以前完全一样:

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

我添加了以下实现,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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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