
Eine einfache, superschnelle, wiederverwendbare Django-App, die Brute-Force-Anmeldeversuche blockiert
.. 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: Unterstützte Python-Versionen :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: Unterstützte Django-Versionen
.. 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: Coverage
.. image:: https://readthedocs.org/projects/django-defender/badge/?version=latest :alt: Dokumentationsstatus :target: https://django-defender.readthedocs.io/en/latest/?badge=latest
Eine einfache, wiederverwendbare Django-App, die Brute-Force-Loginversuche blockiert. Ziel ist es, dies so schnell wie möglich zu machen, damit die Loginversuche nicht verlangsamt werden.
Wir verwenden einen Cache, damit für jeden Loginversuch nicht die Datenbank abgefragt werden muss. Die erste Version basiert auf Redis, aber das Ziel ist es, dies konfigurierbar zu machen, sodass Benutzer das Backend verwenden können, das ihren Anforderungen am besten entspricht.
Wenn Sie defender auf Ihrer Website verwenden, reichen Sie einen PR ein, um zur Liste hinzugefügt zu werden.
Die Dokumentation ist auf Read the Docs verfügbar:
https://django-defender.readthedocs.io
Alle Anmeldeversuche in der Datenbank protokollieren
Unterstützung für Reverse Proxies mit verschiedenen Headern für IP-Adressen
Ratenbegrenzung basierend auf
Redis für die Blacklist verwenden
Konfiguration
Redis-Server
Sperrdauer
Anzahl fehlerhafter Versuche vor Sperrung
95% Codeabdeckung
Vollständige Dokumentation
Möglichkeit, Anmeldeversuche in der Datenbank zu speichern
Management-Befehl zum Bereinigen der Datenbanktabelle für Anmeldeversuche
Admin-Seiten
Kann leicht an eine benutzerdefinierte Authentifizierungsmethode angepasst werden.
Signale werden beim Sperren von Benutzername oder IP gesendet
Admin-Seiten
.. 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
Laden Sie den Code herunter und führen Sie die Installation auf eine der folgenden Arten aus, je nach Methode.
Um die produktionsreife Version von PyPI zu installieren:
.. code-block:: bash
pip install django-defender
Um die Entwicklungsversion aus dem Quellcode nach dem Herunterladen zu installieren:
.. code-block:: bash
python setup.py install
Um die Entwicklungsversion des Master-Branches aus dem GitHub-Repository zu installieren:
.. code-block:: bash
pip install -e git+http://github.com/kencochran django-defender.git#egg=django_defender-dev
Zuerst müssen Sie dieses Projekt zu Ihrer Liste der INSTALLED_APPS in
settings.py hinzufügen:
.. code-block:: python
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', # ... 'defender', # ... ]
Als nächstes installieren Sie die Middleware FailedLoginMiddleware:
.. code-block:: python
MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'defender.middleware.FailedLoginMiddleware', ]
Wenn Sie die gesperrten Benutzer über das Django-Admin verwalten möchten, fügen Sie
Folgendes zu Ihrer urls.py hinzu:
.. code-block:: python
urlpatterns = [ path('admin/defender/', include('defender.urls')), # defender admin path('admin/', admin.site.urls), # normal admin # your own patterns follow... ]
Migrationen
Sie müssen in Ihrer Datenbank die für den Betrieb notwendigen Tabellen erstellen:
.. code-block:: bash
python manage.py migrate defender
Management-Befehle
cleanup_django_defender
Wenn Sie eine Website mit viel Traffic haben, wird die Tabelle AccessAttempts ziemlich schnell voll. Wenn Sie die Daten nicht zu Prüfzwecken aufbewahren müssen, gibt es einen Management-Befehl, der Ihnen hilft, sie sauber zu halten.
Dieser Befehl prüft Ihre Einstellung DEFENDER_ACCESS_ATTEMPT_EXPIRATION, um zu bestimmen,
welche Datensätze gelöscht werden. Der Standardwert, falls nicht angegeben, ist 24 Stunden.
.. code-block:: bash
$ python manage.py cleanup_django_defender
Sie können dies als täglichen oder wöchentlichen Cron-Job einrichten, um die Tabellengröße gering zu halten:
.. code-block:: bash
24 0 * * * /usr/bin/python manage.py cleanup_django_defender >> /var/log/django_defender_cleanup.log
Leistung
Das Ziel von defender ist es, es so schnell wie möglich zu machen, damit es den Anmeldeprozess nicht verlangsamt. Um sicherzustellen, dass unsere Ziele erreicht werden, benötigen wir eine Möglichkeit, die Anwendung zu testen, um sicherzustellen, dass wir auf dem richtigen Weg sind. Der beste Weg, dies zu tun, ist zu vergleichen, wie schnell ein normaler Django-Login mit defender und django-axes ist.
Der normale Django-Login wäre unsere Basislinie, und wir erwarten, dass er die schnellste der drei Methoden ist, da keine zusätzlichen Überprüfungen stattfinden.
Der defender-Login wäre wahrscheinlich langsamer als der Django-Login und hoffentlich schneller als der django-axes-Login. Ziel ist es, den Unterschied zwischen dem normalen rohen Login und defender so gering wie möglich zu halten.
Die Geschwindigkeit des django-axes-Logins wird wahrscheinlich die langsamste der drei sein, da es mehr Überprüfungen durchführt und viele Datenbankabfragen macht.
Die beste Methode, um die Geschwindigkeit eines Logins zu bestimmen, ist ein Auslastungstest gegen eine Anwendung mit jedem Setup und der Vergleich der Login-Zeiten für jeden Typ.
Auslastungstests
Um sicherzustellen, dass wir alle verschiedenen Arten von Logins abdecken, müssen wir in unserem Auslastungstest mehr als einen Test haben.
#. Nur Erfolge: Wir führen einen Auslastungstest mit ausschließlich erfolgreichen Logins durch.
#. Gemischt: einige erfolgreiche, einige fehlgeschlagene: Wir testen mit einigen erfolgreichen und einigen fehlgeschlagenen Logins, um zu sehen, wie sich die Fehler auf die Leistung auswirken.
#. Nur Fehler: Wir testen mit allen fehlgeschlagenen Logins und sehen den Unterschied in der Leistung.
Wir benötigen eine Beispielanwendung, die wir für den Auslastungstest verwenden können, wobei der einzige Unterschied die Konfiguration ist, bei der wir entweder defender, axes oder keine von beiden laden.
Wir können einen gehosteten Auslastungstestdienst oder etwas wie JMeter verwenden. In jedem Fall müssen wir bei allen Tests konsistent sein. Wenn wir JMeter verwenden, sollten wir unsere JMeter-Konfiguration bereitstellen, damit andere die Tests selbst durchführen können.
Ergebnisse der Auslastungstests
Wir werden die Ergebnisse hier veröffentlichen. Wir werden jeden Test erklären und die Ergebnisse zusammen mit einigen Diagrammen zeigen.
django-axes ist großartig, aber es legt alles in der Datenbank ab, und das verursacht einen Engpass, wenn man viele Daten hat. Es verlangsamt die Authentifizierungsanfragen um bis zu 200-300ms. Das mag für einige Websites nicht viel sein, aber für andere ist es zu lang.
Dies begann als Fork von django-axes, verwendet so viel von deren Code wie möglich, entfernt die nicht benötigten Teile und beschleunigt die Lookups, um den Login zu verbessern.
#. Wenn jemand versucht, sich anzumelden, überprüfen wir zunächst, ob er derzeit gesperrt ist. Wir überprüfen den Benutzernamen, den er zu verwenden versucht, sowie die IP-Adresse. Wenn er gesperrt ist, gehe zu Schritt 5. Wenn nicht gesperrt, gehe zu Schritt 2.
#. Er ist nicht gesperrt, also überprüfen wir, ob der Login gültig war. Wenn gültig, gehe zu Schritt 6. Wenn nicht gültig, gehe zu Schritt 3.
#. Der Anmeldeversuch war nicht gültig. Fügen Sie seinen Benutzernamen und seine IP-Adresse für diesen Versuch zum Cache hinzu. Wenn dies ihn über das Limit bringt, fügen Sie ihn zur gesperrten Liste hinzu und gehen Sie dann zu Schritt 5. Wenn nicht über dem Limit, gehe zu Schritt 4.
#. Der Login war ungültig, aber nicht über dem Limit. Senden Sie ihn zurück zum Anmeldebildschirm, um es erneut zu versuchen.
#. Benutzer ist gesperrt: Senden Sie ihn zur gesperrten Seite, teilen Sie ihm mit, dass er gesperrt ist, und geben Sie eine Schätzung, wann er entsperrt wird.
#. Login ist gültig. Setzen Sie alle fehlgeschlagenen Anmeldeversuche zurück und leiten Sie ihn an sein Ziel weiter.
Defender verwendet den Cache, um die fehlgeschlagenen Versuche zu speichern.
Cache-Schlüssel
Zähler:
Boolesche Werte (wenn vorhanden, ist gesperrt):
Sie haben einige Möglichkeiten, django-defender anzupassen. Diese sollten in Ihrer
settings.py-Datei definiert werden.
DEFENDER_LOGIN_FAILURE_LIMIT\ : Int: Die Anzahl der erlaubten Anmeldeversuche, bevor ein
Datensatz für die fehlgeschlagenen Logins erstellt wird. [Standard: 3\ ]
DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME\ : Int: Die Anzahl der erlaubten Anmeldeversuche
für einen Benutzernamen, bevor ein Datensatz für die fehlgeschlagenen Logins erstellt wird.
[Standard: DEFENDER_LOGIN_FAILURE_LIMIT\ ]
DEFENDER_LOGIN_FAILURE_LIMIT_IP\ : Int: Die Anzahl der erlaubten Anmeldeversuche
von einer IP, bevor ein Datensatz für die fehlgeschlagenen Logins erstellt wird.
[Standard: DEFENDER_LOGIN_FAILURE_LIMIT\ ]
DEFENDER_BEHIND_REVERSE_PROXY\ : Boolean: Befindet sich defender hinter einem Reverse Proxy?
[Standard: False\ ]
DEFENDER_REVERSE_PROXY_HEADER\ : String: Der Name des HTTP-Headers mit Ihrer
Reverse-Proxy-IP-Adresse. [Standard: HTTP_X_FORWARDED_FOR\ ]
\ : Boolean: Sperrt einen Benutzer basierend auf einer Kombination von IP und Benutzername aus. Dies verhindert, dass ein Benutzer anderen Benutzern, die von derselben IP-Adresse aus auf die App zugreifen, den Zugriff verweigert. [Standard: \ ]
Begründung für die Verwendung von DEFENDER_ATTEMPT_COOLOFF_TIME und DEFENDER_LOCKOUT_COOLOFF_TIME
Während die alleinige Verwendung von DEFENDER_COOLOFF_TIME für die meisten Anwendungsfälle
ausreichend ist, möchten Entwickler in einigen spezifischen Szenarien, wie z. B. in einer
hochsicherheitsrelevanten Umgebung, möglicherweise eine feinere Kontrolle darüber haben, wie lange
ungültige Anmeldeversuche "gemerkt" werden, während sie für eine Sperrung in Betracht gezogen werden,
im Vergleich zu der Zeit, die diese Sperrschlüssel tatsächlich vom System gesperrt sind.
DEFENDER_ATTEMPT_COOLOFF_TIME und DEFENDER_LOCKOUT_COOLOFF_TIME ermöglichen genau diese
fein granulare Konfiguration.
Wir können auch ein Beispiel mit niedriger Sicherheit und niedrigem Maßstab nehmen, wie die Website
einer Highschool. Eine solche Website könnte auf einigen Computern der Schule laufen und von den IT-Mitarbeitern
der Schule und den Informatiklehrern (falls sie überhaupt welche haben) verwaltet werden. In diesem Szenario
können wir uns vorstellen, dass es bedeutende Teile der Website gibt, die ohne Authentifizierung zugänglich sind,
aber die Anmeldung auf der Website könnte Zugang zu einigen relativ privilegierten Informationen wie dem Namen,
der E-Mail, den Noten und dem Stundenplan des Schülers bieten. Schließlich werden wir annehmen, dass es eine
Passwort-Zurücksetzen-Funktionalität gibt, die das Konto nach Abschluss entsperrt, da eine E-Mail mit dem Konto
verknüpft ist. In einem solchen Fall könnte man sich vorstellen, dass es nicht notwendig ist, sich über lange
Zeiträume an fehlgeschlagene Logins zu erinnern, da die Anwendung lediglich vor potenziellen
Denial-of-Service-Angriffen schützen möchte. Dies könnte erreicht werden, indem man
DEFENDER_ATTEMPT_COOLOFF_TIME niedrig hält, sagen wir 30 Sekunden, und DEFENDER_LOCKOUT_COOLOFF_TIME
auf etwas viel Höheres wie 600 Sekunden setzt. Indem man DEFENDER_ATTEMPT_COOLOFF_TIME niedrig hält und
böswillige Akteure durch die Einstellung von DEFENDER_LOCKOUT_COOLOFF_TIME auf einen hohen Wert für
erhebliche Zeiträume sperrt, werden schnelle Brute-Force-Loginangriffe dennoch abgewehrt und ihr kleiner
Server hat mehr Platz in seinem Cache für andere Daten. Und durch die Bereitstellung der
Passwort-Zurücksetzen-Funktionalität wie oben beschrieben, könnten diese hypothetischen Administratoren
ihre erforderliche Beteiligung am Entsperren echter Benutzer begrenzen und gleichzeitig die beabsichtigte
Zugänglichkeit ihrer Website beibehalten.
Während das vorherige Beispiel etwas konstruiert ist, wird die volle Leistungsfähigkeit dieser Konfigurationen mit der folgenden Erklärung und dem Beispiel demonstriert.
Wenn DEFENDER_STORE_ACCESS_ATTEMPTS auf True gesetzt ist, kann DEFENDER_LOCKOUT_COOLOFF_TIME auch
als Liste von ganzen Zahlen konfiguriert werden. Wenn es als Liste konfiguriert ist, wird die Anzahl der
vorherigen fehlgeschlagenen Anmeldeversuche für den konfigurierten Sperrschlüssel durch
DEFENDER_LOGIN_FAILURE_LIMIT geteilt, um eine absichtlich überschätzte Anzahl der fehlgeschlagenen
Logins für den durch DEFENDER_ACCESS_ATTEMPT_EXPIRATION definierten Zeitraum zu erzeugen. Dies
stellt sich als Überschätzung heraus, da die Zeit zwischen den fehlgeschlagenen Anmeldeversuchen bei
dieser Berechnung nicht berücksichtigt wird. Obwohl dies hart erscheinen mag, kann der zusätzliche
Schutz gegen langsamere Angriffe in bestimmten Szenarien die potenziellen Unannehmlichkeiten für echte
Benutzer des Systems wert sein.
Ein solches Beispiel könnte eine öffentlich zugängliche Webanwendung sein, die sensible Informationen
ihrer Benutzer beherbergt (sagen wir persönliche Finanzunterlagen). Die Anwendung und die darin
enthaltenen Daten sollten mit minimalen Unterbrechungen zugänglich sein, aber die Sicherheit ist
integraler Bestandteil, sodass Verzögerungen bis zu einem gewissen Punkt toleriert werden können.
Unter diesen Umständen könnten wir den Wunsch haben, einfach DEFENDER_COOLOFF_TIME auf eine sehr
große ganze Zahl oder sogar 0 für maximalen Schutz zu setzen. Dies würde jedoch bedeuten, dass ein
echter Benutzer, der aus dem System ausgesperrt wird, von einem Administrator manuell entsperrt werden
müsste, was natürlich umständlich und teuer ist. Indem wir DEFENDER_ATTEMPT_COOLOFF_TIME auf eine
ausreichend große Zahl setzen, sagen wir 600, und DEFENDER_LOCKOUT_COOLOFF_TIME auf eine Liste
steigender ganzer Zahlen (z. B. [60, 120, 300, 600, 0]) setzen, können wir unsere theoretische
Anwendung vergleichbar schützen, als ob wir einfach DEFENDER_COOLOFF_TIME auf 600 gesetzt hätten,
während wir unsere Benutzer deutlich weniger beeinträchtigen.
defender kann für andere Authentifizierungen als das Django-Authentifizierungssystem verwendet werden.
Z. B. falls die Authentifizierung von django-rest-framework vor Brute-Force-Angriffen geschützt werden
muss, kann eine benutzerdefinierte Authentifizierungsmethode implementiert werden.
Es gibt eine Beispielklasse BasicAuthenticationDefender basierend auf 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]
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
Damit es funktioniert, fügen Sie BasicAuthenticationDefender zu DEFAULT_AUTHENTICATION_CLASSES oberhalb aller anderen Authentifizierungsmethoden in Ihrer settings.py hinzu.
defender kann in Kombination mit django-rest-framework und django-rest-auth integriert werden, die zur Benutzerauthentifizierung verwendet werden können.
Referenz
Nachfolgend finden Sie eine Beispielklasse BasicAuthenticationDefender basierend auf rest_framework.authentication.TokenAuthentication, die die Bibliothek django-rest-auth zur Benutzerauthentifizierung verwendet.
.. 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, )
UserModel = get_user_model()
class BasicAuthenticationDefender(serializers.Serializer):
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]
Damit es funktioniert, fügen Sie BasicAuthenticationDefender zum Wörterbuch REST_AUTH_SERIALIZERS in Ihrer settings.py unter dem Schlüssel LOGIN_SERIALIZER hinzu.
Fügen Sie beispielsweise in Ihrer settings.py die folgende Zeile hinzu:
.. code-block:: python
REST_AUTH_SERIALIZERS = { 'LOGIN_SERIALIZER': '.BasicAuthenticationDefender', }
defender kann für Djangos PasswordResetView angepasst werden, um zu viele Einsendungen zu verhindern.
Wir müssen einige neue Views erstellen, die Djangos eingebaute LoginView, PasswordResetView und PasswordResetConfirmView unterklassen – und diese Views dann in unserer urls.py als Ersatz für Djangos eingebaute Views verwenden.
Die Views blockieren basierend auf der E-Mail-Adresse, die im Passwort-Reset-Formular eingegeben wurde. Dies unterscheidet sich von der Standardimplementierung (die den Benutzernamen verwendet), daher müssen wir darauf achten, nach dem Anmelden und dem abgeschlossenen Passwort-Reset alles zu bereinigen.
.. code-block:: python
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-defender sendet Signale, wenn ein Benutzername oder eine IP-Adresse blockiert wird. So richten Sie Signal-Empfängerfunktionen ein:
.. 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)
Tests können ausgeführt werden, nachdem Sie das Repository geklont und Django installiert haben, wie folgt:
.. code-block:: bash
PYTHONPATH=$PYTHONPATH:$PWD django-admin test defender --settings=defender.test_settings
Mit Code-Abdeckung:
.. 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/*
DEFENDER_LOCK_OUT_BY_IP_AND_USERNAMEFalseDEFENDER_DISABLE_IP_LOCKOUT\ : Boolean: Wenn True, wird die IP-Adresse des Benutzers nicht
gesperrt, sondern nur der Benutzername. [Standard: False]
DEFENDER_DISABLE_USERNAME_LOCKOUT\ : Boolean: Wenn True, werden Benutzernamen nicht gesperrt,
sondern nur IP-Adressen. [Standard: False]
DEFENDER_COOLOFF_TIME\ : Int: Wenn gesetzt, definiert es einen Zeitraum der Inaktivität, nach
dem alte fehlgeschlagene Anmeldeversuche und Benutzername/IP-Sperren vergessen werden.
Eine ganze Zahl wird als Anzahl von Sekunden interpretiert. Bei 0 verfallen weder die
fehlgeschlagenen Anmeldeversuche noch die Benutzername/IP-Sperren. [Standard: 300\ ]
DEFENDER_ATTEMPT_COOLOFF_TIME\ : Int: Wenn gesetzt, überschreibt es den Zeitraum der
Inaktivität, nach dem alte fehlgeschlagene Anmeldeversuche vergessen werden, der durch
DEFENDER_COOLOFF_TIME gesetzt wird. Eine ganze Zahl wird als Anzahl von Sekunden interpretiert.
Bei 0 verfallen die fehlgeschlagenen Anmeldeversuche nicht. [Standard: DEFENDER_COOLOFF_TIME\ ]
DEFENDER_LOCKOUT_COOLOFF_TIME\ : Int oder Liste: Wenn gesetzt, überschreibt es den Zeitraum der
Inaktivität, nach dem Benutzername/IP-Sperren vergessen werden, der durch DEFENDER_COOLOFF_TIME
gesetzt wird. Eine ganze Zahl wird als Anzahl von Sekunden interpretiert.
Eine Liste von ganzen Zahlen wird als Anzahl von Sekunden für Benutzer interpretiert, wobei der
Index der ganzen Zahl die Anzahl der vorherigen Sperren (bis zu einem Maximum) darstellt, die in
den letzten DEFENDER_ACCESS_ATTEMPT_EXPIRATION Stunden aufgetreten sind. Wenn die Eigenschaft
auf 0 oder [] gesetzt ist, verfällt die Benutzername/IP-Sperre nicht.
[Standard: DEFENDER_COOLOFF_TIME\ ]
DEFENDER_LOCKOUT_TEMPLATE\ : String: [Standard: None\ ] Wenn gesetzt, gibt es ein Template
an, das gerendert wird, wenn ein Benutzer gesperrt ist. Das Template erhält die folgenden
Kontextvariablen:
cooloff_time_seconds\ : Die Abkühlzeit in Sekundencooloff_time_minutes\ : Die Abkühlzeit in Minutenfailure_limit\ : Die Anzahl der Fehler, bevor Sie gesperrt werden.DEFENDER_USERNAME_FORM_FIELD\ : String: Der Name des Formularfelds, das Ihre
Benutzernamen enthält. [Standard: username\ ]
DEFENDER_CACHE_PREFIX\ : String: Das Cache-Präfix für Ihre defender-Schlüssel.
[Standard: defender\ ]
DEFENDER_LOCKOUT_URL\ : String: Die URL, zu der Sie weiterleiten möchten, wenn jemand
gesperrt ist.
DEFENDER_REDIS_URL\ : String: Die Redis-URL für defender.
[Standard: redis://localhost:6379/0\ ]
(Beispiel mit Passwort: redis://:mypassword@localhost:6379/0\ )
DEFENDER_REDIS_PASSWORD_QUOTE\ : Boolean: Wenn Sonderzeichen im Redis-Passwort (wie '@')
vorkommen, können wir das Passwort quotieren urllib.parse.quote("password!@#") und auf True
setzen. [Standard: False\ ]
DEFENDER_REDIS_NAME\ : String: Der Name des Caches aus CACHES in Ihren Django-Einstellungen
(z. B. "default"). Wenn gesetzt, wird DEFENDER_REDIS_URL ignoriert.
[Standard: None\ ]
DEFENDER_STORE_ACCESS_ATTEMPTS\ : Boolean: Wenn Sie den Anmeldeversuch in der Datenbank
speichern möchten, setzen Sie dies auf True. Bei False wird er nicht gespeichert.
[Standard: True\ ]
DEFENDER_USE_CELERY\ : Boolean: Wenn Sie Celery verwenden möchten, um den Anmeldeversuch
in der Datenbank zu speichern, setzen Sie dies auf True. Bei False wird er inline gespeichert.
[Standard: False\ ]
DEFENDER_ACCESS_ATTEMPT_EXPIRATION\ : Int: Die Zeitspanne in Stunden, wie lange die
Zugriffsversuchsdatensätze in der Datenbank aufbewahrt werden, bevor der Management-Befehl
sie bereinigt. [Standard: 24\ ]
DEFENDER_GET_USERNAME_FROM_REQUEST_PATH\ : String: Der Importpfad der Funktion, die auf den
Benutzernamen aus der Anfrage zugreift. Wenn Sie eine benutzerdefinierte Funktion verwenden
möchten, um auf den Benutzernamen aus der Anfrage zuzugreifen und ihn zu verarbeiten, können Sie
ihn hier angeben. [Standard: defender.utils.username_from_request\ ]