Статьи

Как реализовать проверку входных данных для ресурсов REST

Как реализовать проверку входных данных для ресурсов REST

Платформа SaaS, над которой я работаю, имеет интерфейс RESTful, который принимает полезные нагрузки XML. Остальные валидация

Реализация ресурсов REST

Для магазина Java, такого как мы, имеет смысл использовать JAX-B для генерации классов JavaBean из схемы XML. Работа с полезными нагрузками XML (и JSON) с использованием JAX-B очень проста в среде JAX-RS, такой как Джерси :

01
02
03
04
05
06
07
08
09
10
11
12
@Path("orders")
public class OrdersResource {
  @POST
  @Consumes({ "application/xml", "application/json" })
  public void place(Order order) {
    // Jersey marshalls the XML payload into the Order
    // JavaBean, allowing us to write type-safe code
    // using Order's getters and setters.
    int quantity = order.getQuantity();
    // ...
  }
}

(Обратите внимание, что вы не должны использовать эти универсальные типы носителей, но это обсуждение другого дня.)

В оставшейся части этого поста предполагается использование JAX-B, но его основной смысл относится и к другим технологиям. Что бы вы ни делали, пожалуйста, не используйте XMLDecoder , так как это открыто для множества уязвимостей .

Защита ресурсов REST

Давайте предположим, что quantity заказа используется для выставления счетов, и мы хотим, чтобы люди не крали наши деньги, вводя отрицательную сумму .

Мы можем сделать это с помощью проверки входных данных , одного из самых важных инструментов в наборе инструментов AppSec . Давайте посмотрим на некоторые способы его реализации.

Проверка ввода с помощью схемы XML

XML-схема Мы можем полагаться на XML-схему для проверки , но XML-схема может проверять только столько.

Проверка отдельных свойств, вероятно, будет работать нормально, но когда мы хотим проверить отношения между свойствами, дела идут плохо. Для максимальной гибкости мы хотели бы использовать Java для выражения ограничений.

Что еще более важно, проверка схемы обычно не является хорошей идеей в службе REST .

Основная цель REST — разделить клиент и сервер, чтобы они могли развиваться отдельно.

Если мы проверим по схеме, то новый клиент, отправляющий новое свойство, сломается со старым сервером, который не понимает новое свойство. Обычно лучше игнорировать свойства, которые вы не понимаете.

JAX-B делает это правильно, а также наоборот: свойства, которые не отправляются старым клиентом, заканчиваются null . Следовательно, новый сервер должен быть осторожным, чтобы правильно обрабатывать null значения.

Проверка входных данных с проверкой bean-компонентов

боб-валидация Если мы не можем использовать проверку схемы, то как насчет использования JSR 303 Bean Validation ?

Jersey поддерживает Bean Validation, добавляя jersey-bean-validation jar к вашему classpath.

Существует неофициальный плагин Maven для добавления аннотаций Bean Validation к классам, сгенерированным JAX-B, но я бы предпочел использовать что-то лучше поддерживаемое, и это работает с Gradle .

Итак, давайте все изменим. Мы разработаем наш JavaBean -компонент и сгенерируем XML-схему из bean-компонента для документации:

1
2
3
4
5
6
@XmlRootElement(name = "order")
public class Order {
  @XmlElement
  @Min(1)
  public int quantity;
}
1
2
3
4
5
6
7
8
9
@Path("orders")
public class OrdersResource {
  @POST
  @Consumes({ "application/xml", "application/json" })
  public void place(@Valid Order order) {
    // Jersey recognizes the @Valid annotation and
    // returns 400 when the JavaBean is not valid
  }
}

Любая попытка POST заказа с неположительным количеством теперь дает статус 400 Bad Request .

Теперь предположим, что мы хотим разрешить клиентам изменять отложенные ордера. Мы будем использовать PATCH или PUT для обновления отдельных свойств заказа, таких как количество:

01
02
03
04
05
06
07
08
09
10
@Path("orders")
public class OrdersResource {
  @Path("{id}")
  @PUT
  @Consumes("application/x-www-form-urlencoded")
  public Order update(@PathParam("id") String id,
      @Min(1) @FormParam("quantity") int quantity) {
    // ...
  }
}

Нам также нужно добавить аннотацию @Min , которая является дублированием. Чтобы сделать это СУХОЙ , мы можем превратить quantity в класс, который отвечает за проверку:

01
02
03
04
05
06
07
08
09
10
11
@Path("orders")
public class OrdersResource {
  @Path("{id}")
  @PUT
  @Consumes("application/x-www-form-urlencoded")
  public Order update(@PathParam("id") String id,
      @FormParam("quantity")
      Quantity quantity) {
    // ...
  }
}
1
2
3
4
5
@XmlRootElement(name = "order")
public class Order {
  @XmlElement
  public Quantity quantity;
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Quantity {
  private int value;
 
  public Quantity() { }
 
  public Quantity(String value) {
    try {
      setValue(Integer.parseInt(value));
    } catch (ValidationException e) {
      throw new IllegalArgumentException(e);
    }
  }
 
  public int getValue() {
    return value;
  }
 
  @XmlValue
  public void setValue(int value)
      throws ValidationException {
    if (value < 1) {
      throw new ValidationException(
          "Quantity value must be positive, but is: "
          + value);
    }
    this.value = value;
  }
}

Нам нужен общедоступный конструктор без аргументов для JAX-B, чтобы иметь возможность демонтировать полезную нагрузку в JavaBean и другой конструктор, который принимает String для работы @FormParam .

setValue() javax.xml.bind.ValidationException чтобы JAX-B прекратил демаршаллинг. Тем не менее, Джерси возвращает 500 Internal Server Error когда он видит исключение.

Мы можем исправить это путем сопоставления исключений проверки с 400 кодами состояния с помощью средства отображения исключений . Пока мы на этом, давайте сделаем то же самое для IllegalArgumentException :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Provider
public class DefaultExceptionMapper
    implements ExceptionMapper<Throwable> {
 
  @Override
  public Response toResponse(Throwable exception) {
    Throwable badRequestException
        = getBadRequestException(exception);
    if (badRequestException != null) {
      return Response.status(Status.BAD_REQUEST)
          .entity(badRequestException.getMessage())
          .build();
    }
    if (exception instanceof WebApplicationException) {
      return ((WebApplicationException)exception)
          .getResponse();
    }
    return Response.serverError()
        .entity(exception.getMessage())
        .build();
  }
 
  private Throwable getBadRequestException(
      Throwable exception) {
    if (exception instanceof ValidationException) {
      return exception;
    }
    Throwable cause = exception.getCause();
    if (cause != null && cause != exception) {
      Throwable result = getBadRequestException(cause);
      if (result != null) {
        return result;
      }
    }
    if (exception instanceof IllegalArgumentException) {
      return exception;
    }
    if (exception instanceof BadRequestException) {
      return exception;
    }
    return null;
  }
 
}

Проверка входных данных по объектам домена

http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-ebook/dp/B00794TAUG/ref=tmm_kin_title_0?ie=UTF8&qid=1376856556&sr=1-1 Несмотря на то, что описанный выше подход будет работать достаточно хорошо для многих приложений, он в корне ошибочен.

На первый взгляд, сторонникам доменно-управляемого дизайна (DDD) может понравиться идея создания класса « Quantity ».

Но классы Order и Quantity не моделируют понятия предметной области; они моделируют REST-представления. Это различие может быть тонким, но оно важно.

DDD имеет дело с понятиями предметной области, в то время как REST имеет дело с представлениями этих понятий. Понятия предметной области открыты, но представления разработаны и подвержены всевозможным компромиссам.

Например, ресурс REST коллекции может использовать пейджинг, чтобы предотвратить отправку слишком большого количества данных по сети. Другой ресурс REST может объединять несколько концепций домена, чтобы сделать протокол клиент-сервер менее разговорчивым.

Ресурс REST может даже вообще не иметь соответствующей концепции домена. Например, POST может возвратить 202 Accepted и указать на ресурс REST, который представляет ход асинхронной транзакции.

вездесущий язык Доменные объекты должны как можно точнее охватить вездесущий язык и быть свободными от компромиссов, чтобы функциональность работала.

С другой стороны, при проектировании ресурсов REST необходимо найти компромиссные решения для удовлетворения нефункциональных требований, таких как производительность, масштабируемость и эволюционируемость.

Вот почему я не думаю, что такой подход, как RESTful Objects, будет работать. (По тем же причинам я не верю в Naked Objects для пользовательского интерфейса.)

Добавление проверки к JavaBean-компонентам, которые являются нашими представлениями ресурсов, означает, что у этих bean-компонентов теперь есть две причины для изменения, что является явным нарушением принципа единой ответственности .

Мы получаем гораздо более чистую архитектуру, когда используем JAX-B JavaBeans только для наших REST-представлений и создаем отдельные доменные объекты, которые обрабатывают проверку.

Внедрение проверки в доменные объекты — это то, что Дэн Берг Джонссон называет « Управляемая доменом безопасность» .

пещерное искусство В этом подходе примитивные типы заменяются объектами значений. (Некоторые люди даже возражают против использования каких-либо String ).

Поначалу может показаться излишним создание целого нового класса, содержащего одно целое число, но я призываю вас попробовать. Вы можете обнаружить, что избавление от примитивной одержимости дает ценность даже вне проверки.

Что вы думаете?

Как вы справляетесь с проверкой ввода в ваших сервисах RESTful? Что вы думаете о доменной безопасности? Пожалуйста, оставьте комментарий.

Ссылка: Как реализовать проверку входных данных Для ресурсов REST от нашего партнера по JCG Ремона Синнема в блоге по разработке безопасного программного обеспечения .