Подтвердить что ты не робот

Как оставить клиента, ожидающего службы Java JAX-RS, чтобы предотвратить DOS

У меня возникла проблема с веб-службой, когда пользователи пытаются угадать идентификаторы приложений, перебирая случайные идентификаторы.

Плохие запросы поступают из случайных IP-адресов, поэтому я не могу просто запретить их IP-адрес (если только я не делаю это динамически, но я пока не занимаюсь этим).

В настоящее время, когда я обнаруживаю клиента, который совершил 10 неудачных попыток ID приложения, я помещал их в список блоков в своем приложении и отклонял дальнейшие запросы от этого IP-адреса в течение дня.

Я хочу свести к минимуму объем работы, которую должен выполнить мой сервер, поскольку плохой клиент будет продолжать отправлять 1000 запросов, даже если они отклоняются. Я знаю, что есть динамические решения для брандмауэра, но сейчас я хочу что-то легко реализовать в своем приложении. В настоящее время я спал в течение 5 секунд, чтобы уменьшить количество вызовов, но то, что я хочу сделать, - это просто не отправлять ответ клиенту, поэтому он должен тайм-аут.

Кто-нибудь знает, как это сделать на Java, в JAX-RS?

Мой сервис похож,

@Path("/api")
public class MyServer {

@GET
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
@Path("/my-request")
public String myRequest(String type,
    @Context HttpServletRequest requestContext,
    @Context HttpServletResponse response) {
...
}

См: Как остановить атаку hack/DOS на веб-API

4b9b3361

Ответ 1

Вы ищете асинхронные ответы, которые поддерживаются JAX-RS. Учебник для Джерси содержит несколько примеров того, как реализовать асинхронный ответ на запрос.

При асинхронных ответах поток, отвечающий за ответ на запрос, освобождается для обработки другого запроса еще до того, как запрос будет завершен. Эта функция активируется добавлением параметра с аннотацией @Suspended. Что вам нужно сделать дополнительно, это зарегистрировать выделенный планировщик, который отвечает за пробуждение ваших запросов после определенного времени ожидания, как в этом примере:

@Path("/api")
public class MyServer {

  private ScheduledExecutorService scheduler = ...;

  @GET
  @Consumes(MediaType.APPLICATION_XML)
  @Produces(MediaType.APPLICATION_XML)
  @Path("/my-request")
  public String myRequest(String type,
                          @Context HttpServletRequest requestContext,
                          @Context HttpServletResponse response,
                          @Suspended AsyncResponse asyncResponse) {
    scheduler.schedule(new Runnable() {
      @Override
      public void run() {
        asyncResponse.resume(...)
      }
    }, 5, TimeUnit.SECOND);
  }
}

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

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

Один из способов сделать это - установить mod_proxy, в котором вы могли бы ответить на прокси с кодом ошибки для вредоносных запросов, и установить очень большой предел повторных попыток для таких запросов.

Ответ 2

Существует много возможных решений, с ограничениями, которые вы предоставили двум возможным решениям:

1) Используйте прокси-сервер, который уже поддерживает ограничение запросов. Я лично использовал Nginx и могу рекомендовать его частично, потому что его просто настроить. Релевантная конфигурация ограничения скорости: Модуль ограничения лимита

2) Используйте Asynchronous JAX-RS, чтобы позволить таймауту обнаруженного вредоносного запроса. Запрос Sane может обрабатываться напрямую. Но остерегайтесь последствий, так как такой подход будет потреблять ресурсы на сервере!

Ответ 3

Я могу предложить переместить логику запрета IP из REST в обычный HTTP-фильтр:

@WebFilter(urlPatterns = "/*", asyncSupported = true)
@WebListener
public class HttpFilter implements Filter {

    @Override
   public void init(FilterConfig filterConfig) throws ServletException {   }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if(denyIP(servletRequest.getRemoteAddr())) {
            AsyncContext asyncContext = servletRequest.startAsync();
            asyncContext.setTimeout(100000);
        }else{
           filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {   }

    private boolean denyIP(String address){
         //todo
         return true;
    }

}

Это дешевле для сервера приложений: без десериализации XML/JSON, без вызова классов REST. Также вы можете заметить, что я никогда не звоню asyncContext.start. Я проверяю сервер Wildfly 8.2. В этом случае Wildfly не использует поток для запроса. Я отправил много запросов, но количество потоков было постоянным.

PS

пытается угадать идентификаторы приложений путем перебора случайных идентификаторов

Это не атака DOS. Это грубая атака.

Ответ 4

Вы можете попробовать asyncContext, поддерживаемый Tomcat 3.0. Эта функция отделяет обработчик и обработчик веб-запросов. В вашем случае поток, который принимает запрос, должен ждать/спать больше, чем время ожидания. Создание такого же потока сна в течение такого длительного времени могло бы их голодать, и это резко повлияло бы на производительность серверов. Итак, асинхронная обработка - это правильный путь.

Я использовал asyncContext с Java single thread executor http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html Это сработало для меня. У меня было аналогичное деловое дело, где мне приходилось издеваться над моим выражением.

См. это для реализации http://peter-braun.org/2013/04/asynchronous-processing-from-servlet-3-0-to-jax-rs-2-0/

Один исполнитель потока не будет есть ресурсы и его идеальный вариант для этого варианта использования.

Ответ 5

Я не пробовал это... просто выстрел в темноте здесь, так что возьмите его с солью. Поэтому проблема заключается в том, что как только вы обнаруживаете что-то подозрительное и помещаете IP-адрес в блочный режим, вы не хотите тратить на это запрос еще одну йоту ресурсов, а также заставлять их тратить время на тайм-аут. Но ваши рамки ответят, если вы выбросите исключение. Как насчет прерывания текущего потока? Вы можете сделать это с помощью Thread.currentThread().interrupt();. Надеемся, что контейнер Java, обрабатывающий запрос, проверяет статус прерывания. Возможно, это не так. Я знаю, что видел, что классы, связанные с IO, не обрабатывают запросы, потому что был установлен прерванный флаг.

EDIT: если прерывание потока не работает, вы также можете попробовать бросить InterruptedException. Это может привести к желаемому влиянию.

Ответ 6

Насколько я знаю, в спецификации Servlet такого механизма нет. Поскольку реализации JAX-RS используют сервлеты (по крайней мере, Джерси и Resteasy), у вас нет стандартного способа достижения этого в Java.

Идея использования асинхронного JAX-RS лучше, чем Thread.sleep(5000), но она по-прежнему будет использовать некоторые ресурсы и не что иное, как способ обработки запроса позже, вместо того, чтобы игнорировать запрос всегда.

Ответ 7

Однажды я решил проблему с помощью создания туннельного приложения TCP/IP.

Это небольшое приложение, которое прослушивает внешний порт (например, порт http 80). В обычных условиях принимаются все принятые вызовы, создавая выделенные объекты сокетов. Эти отдельные сокеты затем вызывают реальный (скрытый) веб-сервер, который работает на том же физическом сервере или на любом сервере в вашей локальной сети.

Сам туннель , как диспетчер, но может действовать как loadbalancer. Он может быть многофункциональным.

Дело в том, что вы работаете на низком уровне с сокетами на этом этапе. Если вы передадите список запрещенных ip-адресов этому приложению, то он может отключать приложения на очень низкоуровневом уровне (даже не принимая их вызовы сокетов вообще).

Вы можете интегрировать это в одно и то же приложение Java. Но я считаю, что более гибко рассматривать это как отдельное приложение.

(Дайте мне знать, если вам нужен какой-то исходный код, у меня может быть некоторый код, чтобы вы начали)