
Un'app django riutilizzabile semplice e super veloce che blocca le persone dai tentativi di login con forza bruta.
.. 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: Versioni Python supportate :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: Versioni Django supportate
.. 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: Copertura
.. image:: https://readthedocs.org/projects/django-defender/badge/?version=latest :alt: Stato della documentazione :target: https://django-defender.readthedocs.io/en/latest/?badge=latest
Un'app riutilizzabile Django semplice che impedisce attacchi di forza bruta ai tentativi di login. L'obiettivo è renderla il più veloce possibile, per non rallentare i tentativi di accesso.
Utilizzeremo una cache in modo da non dover interrogare il database a ogni tentativo di login. La prima versione sarà basata su Redis, ma l'obiettivo è renderlo configurabile in modo che le persone possano usare il backend più adatto alle loro esigenze.
Se usi defender sul tuo sito, invia una PR per aggiungerlo all'elenco.
La documentazione è disponibile su Read the Docs:
https://django-defender.readthedocs.io
Registra tutti i tentativi di login nel database
Supporto per proxy inversi con diverse intestazioni per gli indirizzi IP
Limite di frequenza basato su
Usa Redis per la lista nera
Configurazione
Server Redis
Durata del blocco
Numero di tentativi errati prima del blocco
Copertura del codice al 95%
Documentazione completa
Possibilità di memorizzare i tentativi di login nel database
Comando di amministrazione per pulire la tabella dei tentativi di login
Pagine di amministrazione
Può essere facilmente adattato a metodi di autenticazione personalizzati.
Vengono inviati segnali quando si blocca un nome utente o un IP
Pagine di amministrazione
.. 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
Scarica il codice ed esegui il setup in uno dei seguenti modi, a seconda del metodo.
Per installare la versione pronta per la produzione da PyPI:
.. code-block:: bash
pip install django-defender
Per installare la versione di sviluppo dal codice sorgente dopo il download:
.. code-block:: bash
python setup.py install
Per installare la versione di sviluppo del branch master dal repository GitHub:
.. code-block:: bash
pip install -e git+http://github.com/kencochran django-defender.git#egg=django_defender-dev
Prima di tutto, devi aggiungere questo progetto alla tua lista di INSTALLED_APPS in
settings.py
.. code-block:: python
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', # ... 'defender', # ... ]
Successivamente, installa il middleware FailedLoginMiddleware
.. code-block:: python
MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'defender.middleware.FailedLoginMiddleware', ]
Se vuoi gestire gli utenti bloccati tramite l'amministrazione di Django, aggiungi
quanto segue al tuo urls.py
.. code-block:: python
urlpatterns = [ path('admin/defender/', include('defender.urls')), # admin defender path('admin/', admin.site.urls), # admin normale # i tuoi pattern seguono... ]
Migrazioni
Dovrai creare le tabelle nel tuo database necessarie per il funzionamento.
.. code-block:: bash
python manage.py migrate defender
Comandi di gestione
cleanup_django_defender
Se hai un sito web con molto traffico, la tabella AccessAttempts si riempirà abbastanza rapidamente. Se non hai bisogno di conservare i dati a fini di audit, c'è un comando di gestione per aiutarti a mantenerla pulita.
Esaminerà l'impostazione DEFENDER_ACCESS_ATTEMPT_EXPIRATION per determinare
quali record verranno eliminati. Il valore predefinito, se non specificato, è 24 ore.
.. code-block:: bash
$ python manage.py cleanup_django_defender
Puoi impostarlo come cron job giornaliero o settimanale per mantenere la dimensione della tabella ridotta.
.. code-block:: bash
24 0 * * * /usr/bin/python manage.py cleanup_django_defender >> /var/log/django_defender_cleanup.log
Prestazioni
L'obiettivo di defender è renderlo il più veloce possibile in modo da non rallentare il processo di login. Per assicurarci di raggiungere i nostri obiettivi, abbiamo bisogno di un modo per testare l'applicazione e verificare di essere sulla strada giusta. Il modo migliore per farlo è confrontare la velocità di un normale login Django con defender e django-axes.
Il normale login Django sarebbe la nostra base di riferimento, e ci aspettiamo che sia il più veloce dei 3 metodi, perché non ci sono ulteriori controlli in corso.
Il login con defender sarà molto probabilmente più lento del login Django, e si spera più veloce del login django-axes. L'obiettivo è rendere la differenza il più piccola possibile tra il login normale e defender.
La velocità del login django-axes sarà probabilmente la più lenta dei tre poiché esegue più controlli e molte query al database.
Il modo migliore per determinare la velocità di un login è fare un test di carico contro un applicazione con ciascuna configurazione e confrontare i tempi di login per ogni tipo.
Test di carico
Per assicurarci di coprire tutti i diversi tipi di login, nel nostro test di carico dobbiamo avere più di un test.
#. Tutti successi: Eseguiremo un test di carico solo con login riusciti.
#. Misto: alcuni successi, alcuni fallimenti: Eseguiremo un test di carico con alcuni login riusciti e alcuni fallimenti per vedere come i fallimenti influenzano le prestazioni.
#. Tutti fallimenti: Eseguiremo un test di carico con tutti login falliti e vedremo la differenza di prestazioni.
Avremo bisogno di un'applicazione di esempio da usare per il test di carico, con l'unica differenza nella configurazione in cui carichiamo defender, axes o nessuno di essi.
Possiamo usare un servizio di test di carico ospitato o qualcosa come jmeter. In ogni caso dobbiamo essere coerenti per tutti i test. Se usiamo jmeter, dovremmo avere la nostra configurazione jmeter in modo che altri possano eseguire i test per proprio conto.
Risultati dei test di carico
Pubblicheremo i risultati qui. Spiegheremo ogni test e mostreremo i risultati insieme ad alcuni grafici.
django-axes è ottimo, ma mette tutto nel database e questo causa un collo di bottiglia quando si hanno molti dati. Rallenta le richieste di autenticazione fino a 200-300ms. Per alcuni siti potrebbe non essere molto, ma per altri è troppo.
Questo è iniziato come un fork di django-axes e utilizza il più possibile il loro codice, rimuovendo le parti non necessarie e accelerando le ricerche per migliorare il login.
#. Quando qualcuno tenta di accedere, controlliamo prima se è attualmente bloccato. Controlliamo il nome utente che sta provando a usare e l'indirizzo IP. Se è bloccato, vai al punto 5. Se non è bloccato, vai al punto 2.
#. Non è bloccato, quindi controlliamo se il login è valido. Se valido vai al punto 6. Se non valido, vai al punto 3.
#. Il tentativo di login non era valido. Aggiungi il suo nome utente e indirizzo IP per questo tentativo alla cache. Se questo lo porta oltre il limite, aggiungilo alla lista bloccati, quindi vai al punto 5. Se non supera il limite, vai al punto 4.
#. Il login non era valido, ma non è stato superato il limite. Rimandalo alla schermata di login per riprovare.
#. L'utente è bloccato: invialo alla pagina di blocco, dicendogli che è bloccato e dandogli una stima su quando verrà sbloccato.
#. Il login è valido. Reimposta eventuali tentativi di login falliti e reindirizza alla loro destinazione.
Defender utilizza la cache per salvare i tentativi falliti.
Chiavi della cache
Contatori:
Booleani (se presente, è bloccato):
Hai a disposizione alcune opzioni per personalizzare un po' django-defender.
Queste devono essere definite nel tuo file settings.py.
DEFENDER_LOGIN_FAILURE_LIMIT\ : Int: Il numero di tentativi di login consentiti prima di creare
un record per i login falliti. [Default: 3\ ]
DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME\ : Int: Il numero di tentativi di login consentiti
su un nome utente prima di creare un record per i login falliti. [Default: DEFENDER_LOGIN_FAILURE_LIMIT\ ]
DEFENDER_LOGIN_FAILURE_LIMIT_IP\ : Int: Il numero di tentativi di login consentiti
da un IP prima di creare un record per i login falliti. [Default: DEFENDER_LOGIN_FAILURE_LIMIT\ ]
DEFENDER_BEHIND_REVERSE_PROXY\ : Booleano: Defender è dietro un proxy inverso?
[Default: False\ ]
DEFENDER_REVERSE_PROXY_HEADER\ : Stringa: il nome dell'intestazione http con il tuo
indirizzo IP del proxy inverso [Default: HTTP_X_FORWARDED_FOR\ ]
DEFENDER_LOCK_OUT_BY_IP_AND_USERNAME\ : Booleano: Blocca un utente in base a una combinazione di IP e nome utente. Questo impedisce a un utente di negare l'accesso all'applicazione per tutti gli altri utenti che accedono dallo stesso indirizzo IP. [Default: \ ]
Razionale per l'uso di DEFENDER_ATTEMPT_COOLOFF_TIME e DEFENDER_LOCKOUT_COOLOFF_TIME
Sebbene l'uso di DEFENDER_COOLOFF_TIME da solo sia sufficiente per la maggior parte dei casi d'uso, quando si utilizza defender in alcuni scenari specifici come in un contesto di alta sicurezza, gli sviluppatori potrebbero desiderare un controllo più granulare su per quanto tempo vengono "ricordati" i tentativi di login non validi mentre sono in considerazione per il blocco rispetto al tempo in cui quelle chiavi di blocco sono effettivamente bloccate dal sistema.
DEFENDER_ATTEMPT_COOLOFF_TIME e DEFENDER_LOCKOUT_COOLOFF_TIME consentono proprio questa configurazione granulare.
Possiamo anche prendere un esempio a bassa sicurezza e bassa scala come il sito web di una scuola superiore. Un tale sito potrebbe essere gestito su alcuni computer della scuola e amministrato dal personale IT della scuola e dagli insegnanti di informatica (se abbastanza fortunati da averne). In questo scenario possiamo immaginare che ci siano porzioni significative del sito web accessibili senza autenticazione, ma accedere al sito potrebbe fornire accesso a informazioni relativamente privilegiate come nome, email, voti e orario delle lezioni dello studente. Infine, poiché c'è un'email collegata all'account, assumeremo che esista una funzionalità di reimpostazione della password che sblocca l'account al completamento. In tal caso, si potrebbe immaginare che non sia necessario ricordare i login falliti per lunghi periodi di tempo poiché l'applicazione desidererebbe semplicemente proteggersi da potenziali attacchi denial of service. Ciò potrebbe essere ottenuto mantenendo DEFENDER_ATTEMPT_COOLOFF_TIME basso, diciamo 30 secondi, e impostando DEFENDER_LOCKOUT_COOLOFF_TIME a qualcosa di molto più alto come 600 secondi. Mantenendo DEFENDER_ATTEMPT_COOLOFF_TIME basso e bloccando i malintenzionati per periodi di tempo significativi impostando DEFENDER_LOCKOUT_COOLOFF_TIME alto, gli attacchi di forza bruta rapidi verranno comunque sconfitti e il loro piccolo server avrà più spazio nella cache per altri dati. E fornendo la funzionalità di reimpostazione della password come descritto sopra, questi ipotetici amministratori potrebbero limitare il loro coinvolgimento richiesto nello sbloccare utenti reali mantenendo al contempo l'accessibilità prevista del loro sito web.
Sebbene l'esempio precedente sia un po' artificioso, la piena potenza di queste configurazioni è dimostrata con la seguente spiegazione ed esempio.
Quando DEFENDER_STORE_ACCESS_ATTEMPTS è True, DEFENDER_LOCKOUT_COOLOFF_TIME può anche essere configurato come una lista di interi. Quando configurato come lista,
il numero di tentativi di login falliti precedenti per la chiave di blocco configurata viene diviso per DEFENDER_LOGIN_FAILURE_LIMIT per produrre un conteggio intenzionalmente sovrastimato
del numero di login falliti per il periodo definito da DEFENDER_ACCESS_ATTEMPT_EXPIRATION. Questo risulta essere una sovrastima perché il tempo tra i tentativi di login falliti
non viene considerato quando si esegue questo calcolo. Sebbene possa sembrare severo, in alcuni scenari specifici la protezione aggiuntiva contro attacchi più lenti può valere il potenziale disagio causato agli utenti reali del sistema.
Un esempio di ciò potrebbe essere un'applicazione web pubblica accessibile che ospita informazioni sensibili dei suoi utenti (diciamo registri finanziari personali).
L'applicazione e i dati in essa contenuti dovrebbero essere accessibili con interruzioni minime, tuttavia la sicurezza è integrale, quindi i ritardi possono essere tollerati fino a un certo punto.
In queste circostanze potremmo desiderare di impostare semplicemente DEFENDER_COOLOFF_TIME a un intero molto grande o addirittura a 0 per la massima protezione. Ma questo significherebbe che
se un utente reale viene bloccato dal sistema, avremo bisogno di un amministratore per sbloccarlo manualmente, il che è ovviamente scomodo e costoso.
Impostando DEFENDER_ATTEMPT_COOLOFF_TIME a un numero sufficientemente grande, ad esempio 600, e impostando DEFENDER_LOCKOUT_COOLOFF_TIME a una lista di interi crescenti (es. [60, 120, 300, 600, 0]) possiamo
proteggere la nostra applicazione teorica in modo comparabile a se avessimo semplicemente impostato DEFENDER_COOLOFF_TIME a 600 disturbando i nostri utenti in modo significativamente minore.
defender può essere utilizzato per l'autenticazione diversa dal sistema di autenticazione Django.
Ad esempio, se l'autenticazione django-rest-framework deve essere protetta da attacchi di forza bruta, è possibile implementare un metodo di autenticazione personalizzato.
C'è una classe campione BasicAuthenticationDefender basata su 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
Per farlo funzionare, aggiungi BasicAuthenticationDefender a DEFAULT_AUTHENTICATION_CLASSES sopra tutti gli altri metodi di autenticazione nel tuo settings.py.
defender può essere integrato con la combinazione di django-rest-framework e django-rest-auth che possono essere utilizzati per autenticare gli utenti.
Riferimento
Di seguito è riportato un esempio della classe BasicAuthenticationDefender basata su rest_framework.authentication.TokenAuthentication che utilizza la libreria django-rest-auth per l'autenticazione degli utenti.
.. 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]
Per farlo funzionare, aggiungi BasicAuthenticationDefender al dizionario REST_AUTH_SERIALIZERS nel tuo settings.py sotto la chiave LOGIN_SERIALIZER.
Ad esempio, nel tuo settings.py aggiungi la riga seguente,
.. code-block:: python
REST_AUTH_SERIALIZERS = { 'LOGIN_SERIALIZER': '.BasicAuthenticationDefender', }
defender può essere adattato per il PasswordResetView di Django per prevenire troppi invii.
Dobbiamo creare alcune nuove viste che ereditano da LoginView, PasswordResetView e PasswordResetConfirmView integrati di Django — quindi utilizzare queste viste nel nostro urls.py come sostituti di quelle integrate.
Le viste bloccano in base all'indirizzo email inserito nel modulo di reimpostazione password. Questo è diverso dall'implementazione predefinita (che utilizza il nome utente), quindi dobbiamo fare attenzione a pulire dopo di noi al login e al completamento della reimpostazione password.
.. 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 invierà segnali quando blocca un nome utente o un indirizzo IP. Per impostare le funzioni riceventi dei segnali:
.. 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)
I test possono essere eseguiti, dopo aver clonato il repository e aver installato Django, in questo modo:
.. code-block:: bash
PYTHONPATH=$PYTHONPATH:$PWD django-admin test defender --settings=defender.test_settings
Con copertura del codice:
.. 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/*
FalseDEFENDER_DISABLE_IP_LOCKOUT\ : Booleano: Se True, non bloccherà l'indirizzo IP dell'utente, bloccherà solo il nome utente. [Default: False]
DEFENDER_DISABLE_USERNAME_LOCKOUT\ : Booleano: Se True, non bloccherà i nomi utente, bloccherà solo gli indirizzi IP. [Default: False]
DEFENDER_COOLOFF_TIME\ : Int: Se impostato, definisce un periodo di inattività dopo il quale
i vecchi tentativi di login falliti e i blocchi di nome utente/IP verranno dimenticati. Un intero,
sarà interpretato come un numero di secondi. Se 0, né i tentativi di login falliti
né i blocchi di nome utente/IP scadranno. [Default: 300\ ]
DEFENDER_ATTEMPT_COOLOFF_TIME\ : Int: Se impostato, sovrascrive il periodo di inattività
dopo il quale i vecchi tentativi di login falliti verranno dimenticati impostato da DEFENDER_COOLOFF_TIME.
Un intero, sarà interpretato come un numero di secondi. Se 0, i tentativi di login falliti
non scadranno. [Default: DEFENDER_COOLOFF_TIME\ ]
DEFENDER_LOCKOUT_COOLOFF_TIME\ : Int o Lista: Se impostato, sovrascrive il periodo di
inattività dopo il quale i blocchi di nome utente/IP verranno dimenticati impostato da
DEFENDER_COOLOFF_TIME. Un intero, sarà interpretato come un numero di secondi.
Una lista di interi, sarà interpretata come un numero di secondi per utenti con
l'indice dell'intero che rappresenta il numero di blocchi precedenti (fino a un massimo) avvenuti
nelle ultime DEFENDER_ACCESS_ATTEMPT_EXPIRATION ore. Se la proprietà è impostata a
0 o [], il blocco di nome utente/IP non scadrà. [Default: DEFENDER_COOLOFF_TIME\ ]
DEFENDER_LOCKOUT_TEMPLATE\ : Stringa: [Default: None\ ] Se impostato, specifica un template da renderizzare quando un utente viene bloccato. Il template riceve le seguenti variabili di contesto:
cooloff_time_seconds\ : Il tempo di raffreddamento in secondicooloff_time_minutes\ : Il tempo di raffreddamento in minutifailure_limit\ : Il numero di fallimenti prima di venire bloccati.DEFENDER_USERNAME_FORM_FIELD\ : Stringa: il nome del campo del form che contiene il
nome utente. [Default: username\ ]
DEFENDER_CACHE_PREFIX\ : Stringa: Il prefisso della cache per le tue chiavi defender.
[Default: defender\ ]
DEFENDER_LOCKOUT_URL\ : Stringa: L'URL a cui reindirizzare se qualcuno è bloccato.
DEFENDER_REDIS_URL\ : Stringa: l'url redis per defender.
[Default: redis://localhost:6379/0\ ]
(Esempio con password: redis://:mypassword@localhost:6379/0\ )
DEFENDER_REDIS_PASSWORD_QUOTE\ : Booleano: se ci sono caratteri speciali nella password di redis (come '@'), possiamo quotare la password urllib.parse.quote("password!@#") e impostare a True.
[Default: False\ ]
DEFENDER_REDIS_NAME\ : Stringa: il nome della cache da CACHES nelle tue impostazioni Django (es. "default"). Se impostato, DEFENDER_REDIS_URL verrà ignorato.
[Default: None\ ]
DEFENDER_STORE_ACCESS_ATTEMPTS\ : Booleano: Se vuoi memorizzare il tentativo di login
nel database, impostalo a True. Se False, non viene salvato
[Default: True\ ]
DEFENDER_USE_CELERY\ : Booleano: Se vuoi usare Celery per memorizzare il tentativo di login
nel database, impostalo a True. Se False, viene salvato inline.
[Default: False\ ]
DEFENDER_ACCESS_ATTEMPT_EXPIRATION\ : Int: Periodo di tempo in ore per quanto
mantenere i record dei tentativi di accesso nel database prima che il comando di gestione
li pulisca.
[Default: 24\ ]
DEFENDER_GET_USERNAME_FROM_REQUEST_PATH\ : Stringa: Il percorso di importazione della funzione che ottiene il nome utente dalla richiesta.
Se vuoi usare una funzione personalizzata per ottenere ed elaborare il nome utente dalla richiesta - puoi specificarla qui.
[Default: defender.utils.username_from_request\ ]