如何包装来自 Spring REST 存储库的 JSON 响应?
How to wrap JSON response from Spring REST repository?
我有一个 spring REST 控制器,其中 returns 以下 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);
}
现在纪念品 class 是一个 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_outside and 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 攻击,但我不喜欢包装器 class 的冗长。我当然可以用泛型概括上述方法。在 Spring 生态系统中是否有另一种方法可以达到相同的结果(带有注释或类似的东西)?一个想法是行为是由 Spring 自动完成的,所以只要有一个 REST 控制器 returns 一个对象列表,它就可以将这些对象包装在一个对象包装器中,这样就不会直接列出对象被序列化?
我最终得到了以下解决方案(感谢@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 包装在这样的包装器中,因为框架完成了这一部分作品。
我有一个 spring REST 控制器,其中 returns 以下 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);
}
现在纪念品 class 是一个 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_outside and 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 攻击,但我不喜欢包装器 class 的冗长。我当然可以用泛型概括上述方法。在 Spring 生态系统中是否有另一种方法可以达到相同的结果(带有注释或类似的东西)?一个想法是行为是由 Spring 自动完成的,所以只要有一个 REST 控制器 returns 一个对象列表,它就可以将这些对象包装在一个对象包装器中,这样就不会直接列出对象被序列化?
我最终得到了以下解决方案(感谢@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 包装在这样的包装器中,因为框架完成了这一部分作品。