Как обернуть ответ JSON из репозитория Spring REST?

У меня есть контроллер 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);
}

Теперь класс Souvenir — это 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 и 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, который возвращает список объектов, он может обернуть эти объекты в оболочку объекта, чтобы прямой список объектов не был сериализован?


person Mirco Widmer    schedule 29.10.2016    source источник
comment
Весенняя альтернатива? Что ты говоришь? У Struts есть, у Spring нет. Разработайте свой вопрос. Только оболочка - это шляпа, которую Spring имеет для вас, или вы можете расширять сообщения на лету, если они не финальные.   -  person Roman C    schedule 29.10.2016
comment
Вот: docs.spring.io/spring/docs/current/javadoc-api/org/. В вашей собственной реализации вы можете обернуть его в любую структуру, которая вам нужна.   -  person Rafal G.    schedule 30.10.2016
comment
Какой интересный вопрос .. Вы можете попробовать поиграть с @ControllerAdvice, чтобы вернуть свою общую оболочку, если фактическое значение является итерируемым, или вы можете попытаться взломать десериализатор, возможно, конвертер сообщений Jackson Http в вашем случае. Держать нас в курсе :)   -  person Vadim Kirilchuk    schedule 30.10.2016


Ответы (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<Souvenir> souvenirs(@PathVariable("user") String user)), а будущим контроллерам не придется беспокоиться об обертывании своих итерируемых объектов в такую ​​оболочку, потому что эту часть работы выполняет фреймворк.

person Mirco Widmer    schedule 30.10.2016
comment
Рад, что это помогло вам. Здесь следует отметить одну вещь: это нормально по соображениям безопасности, но может быть очень полезно, если вы создадите оболочки для коллекций вручную и, например, укажете размер коллекции. {size:10, сувениры:[..]} Есть и другие возможности, когда вы создаете объект-оболочку вручную. - person Vadim Kirilchuk; 01.11.2016