.. 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再利用可能アプリです。
目標は可能な限り高速に動作させることで、ログイン処理を遅くしないことです。
データベースへの問い合わせを毎回行わずに済むよう、キャッシュを使用します。最初のバージョンはRedisベースですが、将来的には設定可能にして、ユーザーが自身の環境に最適なバックエンドを選択できるようにすることを目指しています。
あなたのサイトでdefenderを使用している場合は、PRを送信してリストに追加してください。
ドキュメントはRead the Docsで公開されています:
https://django-defender.readthedocs.io
すべてのログイン試行をデータベースに記録
IPアドレスに異なるヘッダーを使用するリバースプロキシに対応
以下の条件に基づくレート制限
ブラックリストにRedisを使用
設定項目
Redisサーバー
ブロック時間
ブロック前の不正試行回数
コードカバレッジ95%
完全なドキュメント
ログイン試行をデータベースに保存する機能
ログイン試行データベーステーブルをクリーンアップする管理コマンド
管理ページ
カスタム認証方法に容易に適応可能
ユーザー名またはIPをブロックする際にシグナルが送信される
管理ページ
.. 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: 管理画面のスクリーンショット
.. 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: 管理画面のスクリーンショット
コードをダウンロードし、方法に応じて以下のいずれかの方法でセットアップを実行します。
PyPIからプロダクション対応バージョンをインストールする場合:
.. code-block:: bash
pip install django-defender
ソースコードから開発バージョンをインストールする場合(ダウンロード後):
.. code-block:: bash
python setup.py install
GitHubリポジトリからmasterブランチの開発バージョンをインストールする場合:
.. code-block:: bash
pip install -e git+http://github.com/kencochran django-defender.git#egg=django_defender-dev
まず、このプロジェクトを settings.py の INSTALLED_APPS リストに追加する必要があります。
.. code-block:: python
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', # ... 'defender', # ... ]
次に、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 admin path('admin/', admin.site.urls), # normal admin # your own patterns follow... ]
マイグレーション
運用に必要なテーブルをデータベースに作成する必要があります。
.. 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
24 0 * * * /usr/bin/python manage.py cleanup_django_defender >> /var/log/django_defender_cleanup.log
パフォーマンス
defender の目標は、ログインプロセスを遅くしないよう可能な限り高速にすることです。
目標を達成しているか確認するため、アプリケーションをテストする方法が必要です。
最良の方法は、通常のDjangoログインとdefender、django-axesの速度を比較することです。
通常のDjangoログインがベースラインであり、追加のチェックがないため、3つの方法の中で最も高速であると予想されます。
defenderを使用したログインはおそらくDjangoログインより遅くなりますが、django-axesよりは高速であることを期待します。
目標は、通常の生のログインとdefenderとの差をできるだけ小さくすることです。
django-axesのログイン速度は、チェックが多くデータベースクエリも多いため、3つの中で最も遅くなるでしょう。
ログイン速度を測定する最良の方法は、各セットアップでアプリケーションに対して負荷テストを行い、各タイプのログイン時間を比較することです。
負荷テスト
すべてのタイプのログインをカバーするため、負荷テストでは複数のテストを行う必要があります。
#. すべて成功:成功ログインのみの負荷テスト。
#. 混合(一部成功、一部失敗):成功と失敗を混在させ、失敗がパフォーマンスに与える影響を確認。
#. すべて失敗:すべて失敗ログインの負荷テストで、パフォーマンスの違いを確認。
負荷テスト用のサンプルアプリケーションが必要で、唯一の違いはdefender、axes、またはどちらも使用しない設定のみとします。
ホスト型の負荷テストサービスやjmeterのようなツールを使用できます。
いずれの場合でも、すべてのテストで一貫性を保つ必要があります。
jmeterを使用する場合は、他の人が自分でテストを実行できるように、jmeterの設定ファイルも提供すべきです。
負荷テストの結果
結果はここに掲載します。各テストの説明と、結果をグラフとともに示します。
django-axesは優れていますが、すべてをデータベースに格納するため、データ量が多いとボトルネックになります。
認証リクエストが200〜300msも遅くなることがあります。
サイトによってはそれほど問題にならないかもしれませんが、他のサイトでは長すぎます。
これはdjango-axesのフォークとして始まり、可能な限りコードを再利用し、不要な部分を削除し、ルックアップを高速化してログインを改善しています。
#. 誰かがログインしようとすると、まずそのユーザーが現在ブロックされているかどうかを確認します。
使用しようとしているユーザー名とIPアドレスをチェックします。ブロックされていれば手順5へ。ブロックされていなければ手順2へ。
#. ブロックされていないので、ログインが有効かどうかを確認します。有効なら手順6へ。無効なら手順3へ。
#. ログイン試行が無効でした。この試行のユーザー名とIPアドレスをキャッシュに追加します。
これが制限を超えた場合、ブロックリストに追加し、手順5へ。制限を超えていない場合は手順4へ。
#. ログインは無効でしたが、制限を超えていません。ログイン画面に戻して再試行させます。
#. ユーザーがブロックされました:ブロックページに遷移させ、ブロックされていることと、ブロック解除の推定時間を伝えます。
#. ログインが有効です。失敗したログイン試行をリセットし、目的のページに転送します。
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\ : 文字列:リバースプロキシのIPアドレスを含むHTTPヘッダーの名前。 [デフォルト: 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スタッフや(幸運にもいれば)コンピュータサイエンスの教師が管理しているかもしれません。このシナリオでは、認証なしでアクセスできるウェブサイトの部分も多いでしょうが、ログインすると学生の名前、メールアドレス、成績、授業スケジュールなどの比較的機密性の高い情報にアクセスできるようになります。最後に、アカウントにメールがリンクされているため、パスワードリセット機能があり、リセットが完了するとアカウントのブロックが解除されると仮定します。このような場合、アプリケーションは単にDoS攻撃に対する保護を目的としているため、失敗ログインを長時間記憶する必要はないでしょう。これは、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 で定義された期間内の失敗ログイン回数を意図的に過大評価した数値を生成します。これは、失敗ログイン試行間の時間を考慮しないため、過大評価になります。これは厳しいように思えるかもしれませんが、特定のシナリオでは、低速攻撃に対する追加の保護が、実際のユーザーに生じる可能性のある不便さを上回る価値があります。
そのような例の一つとして、ユーザーの機密情報(例えば個人の財務記録)を保存する、公開Webアプリケーションが考えられます。アプリケーションとそのデータは最小限の中断でアクセス可能であるべきですが、セキュリティは不可欠であるため、ある程度の遅延は許容されます。このような状況では、単に 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 の認証をブルートフォース攻撃から保護する必要がある場合、カスタム認証メソッドを実装できます。
以下は djangorestframework.BasicAuthentication に基づいた BasicAuthenticationDefender クラスのサンプルです:
.. 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
動作させるには、BasicAuthenticationDefender を settings.py の DEFAULT_AUTHENTICATION_CLASSES に、他のすべての認証メソッドよりも上に追加します。
defender は、django-rest-framework と django-rest-auth の組み合わせで組み込むことができ、ユーザー認証に使用できます。
参考
以下は、django-rest-auth ライブラリを使用してユーザー認証を行う rest_framework.authentication.TokenAuthentication に基づいた BasicAuthenticationDefender クラスのサンプルです。
.. 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]
動作させるには、BasicAuthenticationDefender を settings.py の REST_AUTH_SERIALIZERS 辞書に、キー LOGIN_SERIALIZER の下に追加します。
例えば、settings.py に以下の行を追加します:
.. code-block:: python
REST_AUTH_SERIALIZERS = { 'LOGIN_SERIALIZER': '.BasicAuthenticationDefender', }
defender は、Django の PasswordResetView に適用して、過剰な送信を防止できます。
Django の組み込み LoginView、PasswordResetView、PasswordResetConfirmView をサブクラス化する新しいビューを作成し、それらのビューを Django の組み込みの代わりに urls.py で使用する必要があります。
ビューは、パスワードリセットビューで送信されたメールアドレスに基づいてブロックします。これはデフォルトの実装(ユーザー名を使用)とは異なるため、サインイン時およびパスワードリセット完了後に後片付けを注意深く行う必要があります。
.. 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 は、ユーザー名または 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/*
FalseDEFENDER_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\ : 整数またはリスト:設定すると、DEFENDER_COOLOFF_TIME で設定されたユーザー名/IPロックアウトの忘却期間を上書きします。整数値は秒数と解釈されます。整数のリストは、過去の 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\ : 文字列:defenderのRedis URL。 [デフォルト: redis://localhost:6379/0\ ]
(パスワード付きの例: redis://:mypassword@localhost:6379/0\ )
DEFENDER_REDIS_PASSWORD_QUOTE\ : ブール値:Redisパスワードに特殊文字(@など)が含まれる場合、urllib.parse.quote("password!@#") でパスワードをクォートし、Trueに設定します。 [デフォルト: False\ ]
DEFENDER_REDIS_NAME\ : 文字列:Django設定の CACHES 内のキャッシュ名(例:"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\ ]