Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
django-defender — Простое сверхбыстрое переиспользуемое приложение Django, которое блокирует попытки брутфорса входа. | Kitploit
Инструменты/GitHubGitHub/jazzband/django-defender
Оборонительные ИнструментыАтаки на ПаролиВеб-безопасностьАутентификация
GitHubjazzband/django-defender

django-defender

Простое сверхбыстрое переиспользуемое приложение Django, которое блокирует попытки брутфорса входа.

Репозиторий
1.1k1446 месяцев назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

django-defender

.. image:: https://jazzband.co/static/img/badge.svg :target: https://jazzband.co/ :alt: Jazzband

.. image:: https://img.shields.io/pypi/pyversions/django-defender.svg :alt: Поддерживаемые версии Python :target: https://pypi.org/project/django-defender/

.. image:: https://img.shields.io/pypi/djversions/django-defender.svg :target: https://pypi.org/project/django-defender/ :alt: Поддерживаемые версии Django

.. image:: https://github.com/jazzband/django-defender/workflows/Test/badge.svg :target: https://github.com/jazzband/django-defender/actions :alt: GitHub Actions

.. image:: https://codecov.io/gh/jazzband/django-defender/branch/master/graph/badge.svg :target: https://codecov.io/gh/jazzband/django-defender :alt: Покрытие

.. image:: https://readthedocs.org/projects/django-defender/badge/?version=latest :alt: Статус документации :target: https://django-defender.readthedocs.io/en/latest/?badge=latest

Простое переиспользуемое Django-приложение, которое блокирует попытки подбора пароля (brute force). Цель — сделать его максимально быстрым, чтобы не замедлять процесс входа.

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

Сайты, использующие django-defender

Если вы используете defender на своём сайте, отправьте PR, чтобы добавить его в список.

  • https://hub.docker.com
  • https://www.mycosbuilder.com

Документация

Документация доступна на Read the Docs:

https://django-defender.readthedocs.io

Возможности

  • Логирование всех попыток входа в базу данных

  • Поддержка обратных прокси с различными заголовками для IP-адресов

  • Ограничение скорости на основе

    • Имя пользователя
    • IP-адрес
  • Использование Redis для чёрного списка

  • Конфигурация

    • Redis сервер

      • Хост
      • Порт
      • База данных
      • Пароль
      • Префикс ключей
    • Время блокировки

    • Количество неверных попыток до блокировки

  • 95% покрытие кода

  • Полная документация

  • Возможность сохранять попытки входа в базу данных

  • Команда управления для очистки таблицы попыток входа

  • Страницы администрирования

    • Список заблокированных имён пользователей и IP-адресов
    • Список последних попыток входа
    • Возможность разблокировать пользователей
  • Легко адаптируется под кастомные методы аутентификации.

  • При блокировке имени пользователя или IP отправляются сигналы (signals)

Страницы администрирования


.. image:: https://cloud.githubusercontent.com/assets/261601/5950540/8895b570-a729-11e4-9dc3-6b00e46c8043.png :target: https://cloud.githubusercontent.com/assets/261601/5950540/8895b570-a729-11e4-9dc3-6b00e46c8043.png :alt: alt tag

.. image:: https://cloud.githubusercontent.com/assets/261601/5950541/88a35194-a729-11e4-981b-3a55b44ef9d5.png :target: https://cloud.githubusercontent.com/assets/261601/5950541/88a35194-a729-11e4-981b-3a55b44ef9d5.png :alt: alt tag

Требования

  • Python: 3.8, 3.9, 3.10, 3.11, 3.12, PyPy
  • Django: 3.2, 4.2, 5.0, 5.1, 5.2
  • Redis: 5.x, 6.x, 7.x

Установка

Скачайте код и запустите setup одним из следующих способов.

Чтобы установить готовую к использованию версию из PyPI:

.. code-block:: bash

pip install django-defender

Чтобы установить версию для разработки из исходного кода после загрузки:

.. code-block:: bash

python setup.py install

Чтобы установить версию для разработки из ветки master из репозитория GitHub:

.. code-block:: bash

pip install -e git+http://github.com/kencochran django-defender.git#egg=django_defender-dev

Прежде всего, необходимо добавить этот проект в список INSTALLED_APPS в settings.py

.. code-block:: python

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', # ... 'defender', # ... ]

Затем установите middleware FailedLoginMiddleware

.. code-block:: python

MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'defender.middleware.FailedLoginMiddleware', ]

Если вы хотите управлять заблокированными пользователями через админку Django, добавьте следующее в ваш urls.py

.. code-block:: python

urlpatterns = [ path('admin/defender/', include('defender.urls')), # админка defender path('admin/', admin.site.urls), # обычная админка # ваши собственные шаблоны далее... ]

Миграции


Вам потребуется создать таблицы в базе данных, необходимые для работы.

.. code-block:: bash

python manage.py migrate defender

Команды управления


cleanup_django_defender

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

Она будет проверять вашу настройку DEFENDER_ACCESS_ATTEMPT_EXPIRATION, чтобы определить, какие записи будут удалены. По умолчанию, если не указано, — 24 часа.

.. code-block:: bash

$ python manage.py cleanup_django_defender

Вы можете настроить это как ежедневную или еженедельную задачу cron, чтобы уменьшить размер таблицы.

.. code-block:: bash

запуск в 12:24 каждое утро.

24 0 * * * /usr/bin/python manage.py cleanup_django_defender >> /var/log/django_defender_cleanup.log

Долгосрочные цели

  • Подключаемые бэкенды, чтобы можно было использовать что-то кроме Redis
  • Отправка email пользователям при блокировке их учётной записи
  • Добавление белого списка для имён пользователей и IP, которые никогда не будут блокироваться (администраторы и т.д.)
  • Добавление постоянного чёрного списка для IP-адресов
  • Сканирование известных прокси-IP и не блокировка запросов, поступающих с них (повышение вероятности, что хороший IP не будет заблокирован)
  • Добавление команды управления для удаления старых (настраиваемых) попыток входа.

Производительность


Цель defender — сделать его максимально быстрым, чтобы он не замедлял процесс входа. Чтобы убедиться, что мы достигаем наших целей, нам нужен способ тестирования приложения. Лучший способ — сравнить скорость обычного входа в Django с defender и django-axes.

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

Вход с defender, скорее всего, будет медленнее, чем вход в Django, и, надеемся, быстрее, чем вход с django-axes. Цель — сделать разницу между обычным входом и defender как можно меньше.

Скорость входа с django-axes, вероятно, будет самой медленной из трёх, поскольку выполняется больше проверок и много запросов к базе данных.

Лучший способ определить скорость входа — провести нагрузочное тестирование приложения с каждой конфигурацией и сравнить время входа для каждого типа.

Нагрузочное тестирование


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

#. Все успешные: Load-тест только с успешными входами.

#. Смешанные: часть успешных, часть неудачных: Load-тест с некоторыми успешными и некоторыми неудачными входами, чтобы увидеть, как неудачи влияют на производительность.

#. Все неудачные: Load-тест со всеми неудачными входами и посмотреть разницу в производительности.

Нам понадобится пример приложения, которое мы будем использовать для нагрузочного теста, единственное отличие — конфигурация, в которой мы загружаем defender, axes или ни один из них.

Мы можем использовать хостинговый сервис нагрузочного тестирования или что-то вроде jmeter. В любом случае нам нужно быть последовательными во всех тестах. Если мы используем jmeter, мы должны предоставить нашу конфигурацию jmeter для того, чтобы другие могли запустить тесты самостоятельно.

Результаты нагрузочных тестов


Мы опубликуем результаты здесь. Мы объясним каждый тест и покажем результаты а также некоторые графики.

Почему не django-axes

django-axes отличен, но он всё сохраняет в базу данных, и это вызывает узкое место, когда у вас много данных. Он замедляет запросы аутентификации на 200-300 мс. Для некоторых сайтов это может быть несущественно, но для других — слишком долго.

Этот проект начинался как форк django-axes, и в нём используется как можно больше их кода, а также удаляются ненужные части и ускоряются запросы для улучшения процесса входа.

Как работает django-defender

#. Когда кто-то пытается войти, мы сначала проверяем, не заблокирован ли он в данный момент. Мы проверяем имя пользователя, которое он пытается использовать, а также IP-адрес. Если он заблокирован, переходим к шагу 5. Если не заблокирован, переходим к шагу 2.

#. Он не заблокирован, поэтому мы проверяем, действителен ли вход. Если действителен, переходим к шагу 6. Если недействителен, переходим к шагу 3.

#. Попытка входа была недействительной. Добавляем его имя пользователя и IP-адрес для этой попытки в кеш. Если это превышает лимит, добавляем его в список заблокированных, затем переходим к шагу 5. Если лимит не превышен, переходим к шагу 4.

#. Вход был недействительным, но лимит не превышен. Отправляем его обратно на экран входа для повторной попытки.

#. Пользователь заблокирован: Отправляем его на страницу блокировки, сообщая, что он заблокирован, и указываем примерное время, когда он будет разблокирован.

#. Вход действителен. Сбрасываем все неудачные попытки входа и перенаправляем пользователя к его целевой странице.

Бэкенд кеша

Defender использует кеш для хранения неудачных попыток.

Ключи кеша


Счётчики:

  • prefix:failed:ip:[ip] (счётчик, TTL)
  • prefix:failed:username:[username] (счётчик, TTL)

Булевы значения (если присутствует — заблокирован):

  • prefix:blocked:ip:[ip] (true, TTL)
  • prefix:blocked:username:[username] (true, TTL)

Настройка django-defender

У вас есть несколько доступных опций для настройки django-defender. Они должны быть определены в вашем файле settings.py.

  • DEFENDER_LOGIN_FAILURE_LIMIT\ : Целое число: количество попыток входа, разрешённых до того, как будет создана запись о неудачных входах. [По умолчанию: 3\ ]

  • DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME\ : Целое число: количество попыток входа, разрешённых для имени пользователя, прежде чем будет создана запись о неудачных входах. [По умолчанию: DEFENDER_LOGIN_FAILURE_LIMIT\ ]

  • DEFENDER_LOGIN_FAILURE_LIMIT_IP\ : Целое число: количество попыток входа, разрешённых с IP-адреса, прежде чем будет создана запись о неудачных входах. [По умолчанию: DEFENDER_LOGIN_FAILURE_LIMIT\ ]

  • DEFENDER_BEHIND_REVERSE_PROXY\ : Булево: находится ли defender за обратным прокси? [По умолчанию: False\ ]

  • DEFENDER_REVERSE_PROXY_HEADER\ : Строка: имя HTTP-заголовка с вашим IP-адресом обратного прокси [По умолчанию: HTTP_X_FORWARDED_FOR\ ]

  • DEFENDER_LOCK_OUT_BY_IP_AND_USERNAME\ : Булево: блокирует пользователя на основе комбинации IP и имени пользователя. Это предотвращает ситуацию, когда один пользователь может лишить доступа к приложению всех остальных пользователей, находящихся за одним IP-адресом. [По умолчанию: \ ]

Обоснование использования DEFENDER_ATTEMPT_COOLOFF_TIME и DEFENDER_LOCKOUT_COOLOFF_TIME


Хотя использования DEFENDER_COOLOFF_TIME достаточно для большинства случаев, при использовании defender в некоторых специфических сценариях (например, в среде с высокими требованиями к безопасности) разработчики могут захотеть иметь более точный контроль над тем, как долго "запоминаются" неверные попытки входа, пока рассматривается блокировка, по сравнению со временем, на которое ключи блокировки фактически блокируются в системе. DEFENDER_ATTEMPT_COOLOFF_TIME и DEFENDER_LOCKOUT_COOLOFF_TIME обеспечивают именно такую тонкую настройку.

Рассмотрим также пример с низкими требованиями безопасности и малым масштабом, например, веб-сайт школы. Такой сайт может работать на некоторых школьных компьютерах и администрироваться школьным IT-персоналом и учителями информатики (если повезёт). В этом сценарии можно предположить, что значительные части сайта доступны без аутентификации, но вход на сайт может предоставить доступ к относительно привилегированной информации, такой как имя, электронная почта, оценки и расписание занятий студента. Наконец, поскольку с учётной записью связана электронная почта, предположим, что существует функция сброса пароля, которая разблокирует учётную запись после завершения. В таком случае можно представить, что нет необходимости запоминать неудачные входы в течение длительного времени, поскольку приложение просто хочет защититься от потенциальных атак типа "отказ в обслуживании". Это может быть достигнуто установкой низкого значения DEFENDER_ATTEMPT_COOLOFF_TIME, скажем, 30 секунд, и установкой DEFENDER_LOCKOUT_COOLOFF_TIME на что-то гораздо большее, например 600 секунд. Поддерживая низкое значение DEFENDER_ATTEMPT_COOLOFF_TIME и блокируя злоумышленников на значительное время, установив высокое DEFENDER_LOCKOUT_COOLOFF_TIME, быстрые атаки методом подбора всё равно будут отражены, и на их небольшом сервере будет больше места в кеше для других данных. А предоставив функцию сброса пароля, как описано выше, эти гипотетические администраторы могут ограничить своё участие в разблокировке реальных пользователей, сохраняя при этом желаемую доступность сайта.

Хотя предыдущий пример несколько надуман, полная мощь этих конфигураций демонстрируется следующим объяснением и примером.

Когда DEFENDER_STORE_ACCESS_ATTEMPTS равно True, DEFENDER_LOCKOUT_COOLOFF_TIME также можно настроить как список целых чисел. При настройке в виде списка количество предыдущих неудачных попыток входа для данного ключа блокировки делится на DEFENDER_LOGIN_FAILURE_LIMIT, чтобы получить намеренно завышенное количество неудачных входов за период, определённый DEFENDER_ACCESS_ATTEMPT_EXPIRATION. Это оказывается завышением, потому что время между неудачными попытками входа не учитывается при этом вычислении. Хотя это может показаться жёстким, в некоторых конкретных сценариях дополнительная защита от более медленных атак может стоить потенциальных неудобств для реальных пользователей системы.

Одним из таких примеров может быть публичное веб-приложение, содержащее конфиденциальную информацию своих пользователей (скажем, личные финансовые записи). Приложение и данные в нём должны быть доступны с минимальными перерывами, однако безопасность важна, поэтому задержки могут быть терпимы до определённого момента. В таких обстоятельствах мы можем захотеть просто установить DEFENDER_COOLOFF_TIME в очень большое целое число или даже 0 для максимальной защиты. Но это будет означать, что если реальный пользователь всё же будет заблокирован в системе, нам понадобится администратор, чтобы вручную его разблокировать, что, конечно, обременительно и дорого. Установив DEFENDER_ATTEMPT_COOLOFF_TIME достаточно большим числом, скажем 600, и установив DEFENDER_LOCKOUT_COOLOFF_TIME в виде списка возрастающих целых чисел (например, [60, 120, 300, 600, 0]), мы можем защитить наше теоретическое приложение сравнимо с тем, если бы мы просто установили DEFENDER_COOLOFF_TIME в 600, при этом значительно меньше нарушая работу наших пользователей.

Адаптация к другим методам аутентификации

defender может использоваться для аутентификации, отличной от системы аутентификации Django. Например, если требуется защитить от атак методом подбора аутентификацию django-rest-framework, можно реализовать собственный метод аутентификации.

Ниже приведён пример класса BasicAuthenticationDefender, основанного на djangorestframework.BasicAuthentication\ :

.. code-block:: python

import base64 import binascii

from django.utils.translation import gettext_lazy as _

from rest_framework import HTTP_HEADER_ENCODING, exceptions from rest_framework.authentication import ( BasicAuthentication, get_authorization_header, )

from defender import utils from defender import config

class BasicAuthenticationDefender(BasicAuthentication):def get_username_from_request(self, request): auth = get_authorization_header(request).split() return base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')[0]

root@kitploit:~
   def authenticate(self, request):
       auth = get_authorization_header(request).split()

       if not auth or auth[0].lower() != b'basic':
           return None

       if len(auth) == 1:
           msg = _('Invalid basic header. No credentials provided.')
           raise exceptions.AuthenticationFailed(msg)
       elif len(auth) > 2:
           msg = _('Invalid basic header. Credentials string should not contain spaces.')
           raise exceptions.AuthenticationFailed(msg)

       if utils.is_already_locked(request, get_username=self.get_username_from_request):
           detail = "You have attempted to login {failure_limit} times, with no success." \
                    "Your account is locked for {cooloff_time_seconds} seconds" \
                    "".format(
                       failure_limit=config.FAILURE_LIMIT,
                       cooloff_time_seconds=config.LOCKOUT_COOLOFF_TIME[
                          defender_utils.get_lockout_cooloff_time(username=self.get_username_from_request(request))
                       ]
                    )
           raise exceptions.AuthenticationFailed(_(detail))

       try:
           auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
       except (TypeError, UnicodeDecodeError, binascii.Error):
           msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
           raise exceptions.AuthenticationFailed(msg)

       userid, password = auth_parts[0], auth_parts[2]
       login_unsuccessful = False
       login_exception = None
       try:
           response = self.authenticate_credentials(userid, password)
       except exceptions.AuthenticationFailed as e:
           login_unsuccessful = True
           login_exception = e

       utils.add_login_attempt_to_db(request,
                                     login_valid=not login_unsuccessful,
                                     get_username=self.get_username_from_request)
       # add the failed attempt to Redis in case of a failed login or resets the attempt count in case of success
       utils.check_request(request,
                           login_unsuccessful=login_unsuccessful,
                           get_username=self.get_username_from_request)
       if login_unsuccessful:
           raise login_exception

       return response

Чтобы это заработало, добавьте BasicAuthenticationDefender в DEFAULT_AUTHENTICATION_CLASSES перед всеми остальными методами аутентификации в вашем settings.py.

Адаптация к другим методам аутентификации :- django-rest-auth в djangorestframework

defender можно интегрировать в комбинацию django-rest-framework и django-rest-auth, которые используются для аутентификации пользователей.

Справочная информация


  • https://www.django-rest-framework.org/
  • https://django-rest-auth.readthedocs.io/en/latest/

Ниже представлен пример класса BasicAuthenticationDefender, основанного на rest_framework.authentication.TokenAuthentication, который использует библиотеку django-rest-auth для аутентификации пользователей.

.. code-block:: python

import base64 import binascii

from django.conf import settings from django.contrib.auth import get_user_model, authenticate from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm from django.contrib.auth.tokens import default_token_generator from django.utils.http import urlsafe_base64_decode as uid_decoder from django.utils.translation import gettext_lazy as _ from django.utils.encoding import force_str from rest_framework import serializers, exceptions, HTTP_HEADER_ENCODING from rest_framework.exceptions import ValidationError from defender import utils as defender_utils from defender import config from rest_framework.authentication import ( get_authorization_header, )

Get the UserModel

UserModel = get_user_model()

class BasicAuthenticationDefender(serializers.Serializer):

root@kitploit:~
  username = serializers.CharField(required=False, allow_blank=True)
  email = serializers.EmailField(required=False, allow_blank=True)
  password = serializers.CharField(style={'input_type': 'password'})

  def authenticate(self, **kwargs):
    request = self.context['request']

    if hasattr(settings, 'ACCOUNT_AUTHENTICATION_METHOD'):
        login_field = settings.ACCOUNT_AUTHENTICATION_METHOD
    else:
        login_field = 'username'
    userid = self.username_from_request(request, login_field)

    if defender_utils.is_already_locked(request, username=userid):
        detail = "You have attempted to login {failure_limit} times with no success. "
                 .format(
                     failure_limit=config.FAILURE_LIMIT,
                     cooloff_time_seconds=config.LOCKOUT_COOLOFF_TIME[defender_utils.get_lockout_cooloff_time(username=userid)]
                 )
        raise exceptions.AuthenticationFailed(_(detail))

    login_unsuccessful = False
    login_exception = None
    try:
        response = authenticate(request, **kwargs)
        if response == None:
            login_unsuccessful = True
            msg = _('Unable to log in with provided credentials.')
            # raise exceptions.ValidationError(msg)
            login_exception = exceptions.ValidationError(msg)
    except exceptions.AuthenticationFailed as e:
        login_unsuccessful = True
        login_exception = e

    defender_utils.add_login_attempt_to_db(request,
                                           login_valid=not login_unsuccessful,
                                           username=userid)

    user_not_blocked = defender_utils.check_request(request,
                                                    login_unsuccessful=login_unsuccessful,
                                                    username=userid)
    if user_not_blocked and not login_unsuccessful:
        return response

    raise login_exception

  def _validate_email(self, email, password):
    user = None

    if email and password:
        user = self.authenticate(email=email, password=password)
    else:
        msg = _('Must include "email" and "password".')
        raise exceptions.ValidationError(msg)

    return user

  def _validate_username(self, username, password):
    user = None

    if username and password:
        user = self.authenticate(username=username, password=password)
    else:
        msg = _('Must include "username" and "password".')
        raise exceptions.ValidationError(msg)

    return user

  def _validate_username_email(self, username, email, password):
    user = None

    if email and password:
        user = self.authenticate(email=email, password=password)
    elif username and password:
        user = self.authenticate(username=username, password=password)
    else:
        msg = _('Must include either "username" or "email" and "password".')
        raise exceptions.ValidationError(msg)

    return user

  def validate(self, attrs):
    username = attrs.get('username')
    email = attrs.get('email')
    password = attrs.get('password')

    user = None

    if 'allauth' in settings.INSTALLED_APPS:
        from allauth.account import app_settings

        # Authentication through email
        if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
            user = self._validate_email(email, password)

        # Authentication through username
        elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
            user = self._validate_username(username, password)

        # Authentication through either username or email
        else:
            user = self._validate_username_email(username, email, password)

    else:
        # Authentication without using allauth
        if email:
            try:
                username = UserModel.objects.get(
                    email__iexact=email).username()
            except UserModel.DoesNotExist:
                pass

        if username:
            user = self._validate_username_email(username, '', password)

    # Did we get back an active user?
    if user:
        if not user.is_active:
            msg = _('User account is disabled.')
            raise exceptions.ValidationError(msg)
    else:
        msg = _('Unable to log in with provided credentials.')
        raise exceptions.ValidationError(msg)

    # If required, is the email verified?
    if 'rest_auth.registration' in settings.INSTALLED_APPS:
        from allauth.account import app_settings
        if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
            email_address = user.emailaddress_set.get(email=user.email)
            if not email_address.verified:
                raise serializers.ValidationError(
                    _('E-mail is not verified.'))

    attrs['user'] = user
    return attrs

  def username_from_request(self, request, login_field):
    user_data = request._data
    return user_data[login_field]

Чтобы это заработало, добавьте BasicAuthenticationDefender в словарь REST_AUTH_SERIALIZERS в вашем settings.py под ключом LOGIN_SERIALIZER. Например, в вашем settings.py добавьте следующую строку:

.. code-block:: python

REST_AUTH_SERIALIZERS = { 'LOGIN_SERIALIZER': '.BasicAuthenticationDefender', }

Адаптация для форм сброса пароля

defender можно адаптировать для Django-представления PasswordResetView, чтобы предотвратить слишком много отправок.

Нам нужно создать новые представления, которые наследуют встроенные в Django LoginView, PasswordResetView и PasswordResetConfirmView — а затем использовать эти представления в нашем urls.py как замену встроенным представлениям Django.

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

.. code-block:: python

root@kitploit:~
from defender import utils as def_utils
from django.contrib.auth import views as auth_views

class UserSignIn(auth_views.LoginView):
    def form_valid(self, form):
        """Force clear all the cached Defender statues for the authenticated user’s email address."""
        super_valid = super().form_valid(form)
        def_utils.check_request(self.request, False, username=form.get_user().email)
        return super_valid

class PasswordResetBruteForceProtectedView(auth_views.PasswordResetView):
    def get(self, request, *args, **kwargs):
        """Confirm the user isn’t already blocked by IP before showing the password reset view."""
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """
        Confirm the user isn’t already blocked by IP before allowing form POST.

        Also, force log this form POST as a single entry in the Defender cache, against the submitted email address.
        """
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        def_utils.check_request(
            request, login_unsuccessful=True, username=request.POST.get("email")
        )
        return super().post(request, *args, **kwargs)


class PasswordResetConfirmBruceForceProtectedView(auth_views.PasswordResetConfirmView):
    def get(self, request, *args, **kwargs):
        """Confirm the user isn’t already blocked by IP before showing the password confirm view."""
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """Confirm the user isn’t already blocked by IP before allowing form POST for the password change confirmation."""
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        return super().post(request, *args, **kwargs)

    def form_valid(self, form):
        """Force clear all the cached Defender statues for the user’s email address after successfully changing their password."""
        super_valid = super().form_valid(form)
        def_utils.check_request(
            self.request, login_unsuccessful=False, username=self.user.email
        )
        return super_valid

Сигналы Django

django-defender будет отправлять сигналы при блокировке имени пользователя или IP-адреса. Чтобы настроить функции-приёмники сигналов:

.. code-block:: python

from django.dispatch import receiver

from defender import signals

@receiver(signals.username_block) def username_blocked(username, **kwargs): print("%s was blocked!" % username)

@receiver(signals.ip_block) def ip_blocked(ip_address, **kwargs): print("%s was blocked!" % ip_address)

Запуск тестов

Тесты можно запустить после клонирования репозитория и установки Django следующим образом:

.. code-block:: bash

PYTHONPATH=$PYTHONPATH:$PWD django-admin test defender --settings=defender.test_settings

С покрытием кода:

.. code-block:: bash

PYTHONPATH=$PYTHONPATH:$PWD coverage run --source=defender $(which django-admin) test defender --settings=defender.test_settings

Релиз

#. python setup.py sdist #. twine upload dist/*

Скачать инструмент
False
  • DEFENDER_DISABLE_IP_LOCKOUT\ : Булево: если True, блокировка IP-адреса пользователя не будет выполняться, будет блокироваться только имя пользователя. [По умолчанию: False]

  • DEFENDER_DISABLE_USERNAME_LOCKOUT\ : Булево: если True, блокировка имён пользователей не будет выполняться, будут блокироваться только IP-адреса. [По умолчанию: False]

  • DEFENDER_COOLOFF_TIME\ : Целое число: если установлено, определяет период бездействия, после которого старые неудачные попытки входа и блокировки по имени пользователя/IP будут забыты. Целое число, интерпретируется как количество секунд. Если 0, ни неудачные попытки входа, ни блокировки по имени пользователя/IP не истекают. [По умолчанию: 300\ ]

  • DEFENDER_ATTEMPT_COOLOFF_TIME\ : Целое число: если установлено, переопределяет период бездействия, после которого старые неудачные попытки входа будут забыты, заданный DEFENDER_COOLOFF_TIME. Целое число, интерпретируется как количество секунд. Если 0, неудачные попытки входа не истекают. [По умолчанию: DEFENDER_COOLOFF_TIME\ ]

  • DEFENDER_LOCKOUT_COOLOFF_TIME\ : Целое число или список: если установлено, переопределяет период бездействия, после которого будут забыты блокировки по имени пользователя/IP, заданные DEFENDER_COOLOFF_TIME. Целое число интерпретируется как количество секунд. Список целых чисел интерпретируется как количество секунд для пользователей, где индекс целого числа — это количество предыдущих блокировок (до некоторого максимума), произошедших за последние DEFENDER_ACCESS_ATTEMPT_EXPIRATION часов. Если свойство установлено в 0 или [], блокировка по имени пользователя/IP не истекает. [По умолчанию: DEFENDER_COOLOFF_TIME\ ]

  • DEFENDER_LOCKOUT_TEMPLATE\ : Строка: [По умолчанию: None\ ] Если установлено, указывает шаблон для отображения, когда пользователь заблокирован. Шаблон получает следующие переменные контекста:

    • cooloff_time_seconds\ : Время ожидания в секундах
    • cooloff_time_minutes\ : Время ожидания в минутах
    • failure_limit\ : Количество неудач до блокировки.
  • DEFENDER_USERNAME_FORM_FIELD\ : Строка: имя поля формы, которое содержит имена пользователей. [По умолчанию: username\ ]

  • DEFENDER_CACHE_PREFIX\ : Строка: префикс кеша для ключей defender. [По умолчанию: defender\ ]

  • DEFENDER_LOCKOUT_URL\ : Строка: URL, на который вы хотите перенаправлять, если кто-то заблокирован.

  • DEFENDER_REDIS_URL\ : Строка: redis URL для defender. [По умолчанию: redis://localhost:6379/0\ ] (Пример с паролем: redis://:mypassword@localhost:6379/0\ )

  • DEFENDER_REDIS_PASSWORD_QUOTE\ : Булево: если в пароле Redis есть спецсимволы (например, '@'), можно заквотировать пароль с помощью urllib.parse.quote("password!@#") и установить значение True. [По умолчанию: False\ ]

  • DEFENDER_REDIS_NAME\ : Строка: имя кеша из CACHES в настройках Django (например "default"). Если установлено, DEFENDER_REDIS_URL игнорируется. [По умолчанию: None\ ]

  • DEFENDER_STORE_ACCESS_ATTEMPTS\ : Булево: если вы хотите сохранять попытку входа в базу данных, установите True. Если False, она не сохраняется. [По умолчанию: True\ ]

  • DEFENDER_USE_CELERY\ : Булево: если вы хотите использовать Celery для сохранения попытки входа в базу данных, установите True. Если False, сохранение происходит синхронно. [По умолчанию: False\ ]

  • DEFENDER_ACCESS_ATTEMPT_EXPIRATION\ : Целое число: время в часах, в течение которого хранить записи о попытках входа в базе данных до того, как команда управления их очистит. [По умолчанию: 24\ ]

  • DEFENDER_GET_USERNAME_FROM_REQUEST_PATH\ : Строка: путь импорта функции, которая получает имя пользователя из запроса. Если вы хотите использовать пользовательскую функцию для получения и обработки имени пользователя из запроса — вы можете указать её здесь. [По умолчанию: defender.utils.username_from_request\ ]