
로그인 무차별 대입 공격을 차단하는 간단하고 매우 빠른 Django 재사용 가능 앱
.. 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
로그인 시도를 무차별 대입(brute force)하는 사람들을 차단하는 간단한 Django 재사용 가능 앱입니다. 목표는 로그인 속도를 늦추지 않도록 최대한 빠르게 만드는 것입니다.
각 로그인 시도마다 데이터베이스를 확인하기 위해 데이터베이스에 접근하지 않아도 되도록 캐시를 사용합니다. 첫 번째 버전은 Redis를 기반으로 하지만, 사람들이 자신의 필요에 가장 잘 맞는 백엔드를 사용할 수 있도록 구성 가능하게 만드는 것이 목표입니다.
사이트에서 defender를 사용하고 있다면, 목록에 추가할 PR을 제출해 주세요.
문서는 Read the Docs에서 확인할 수 있습니다:
https://django-defender.readthedocs.io
모든 로그인 시도를 데이터베이스에 기록
IP 주소에 대해 다른 헤더를 사용하는 리버스 프록시 지원
다음 기준으로 속도 제한
블랙리스트에 Redis 사용
구성
Redis 서버
차단 기간
차단 전 허용되는 잘못된 시도 횟수
95% 코드 커버리지
완전한 문서
로그인 시도를 데이터베이스에 저장하는 기능
로그인 시도 데이터베이스 테이블을 정리하는 관리 명령어
관리자 페이지
사용자 정의 인증 방법에 쉽게 적용 가능
사용자 이름 또는 IP 차단 시 신호(signal) 전송
관리자 페이지
.. 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: 대체 태그
코드를 다운로드하고, 방법에 따라 다음 중 하나의 방식으로 setup을 실행합니다.
PyPI에서 프로덕션 준비 버전을 설치하려면:
.. code-block:: bash
pip install django-defender
소스 코드를 다운로드한 후 개발 버전을 설치하려면:
.. code-block:: bash
python setup.py install
GitHub 저장소에서 마스터 브랜치 개발 버전을 설치하려면:
.. 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 관리자 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
이를 매일 또는 매주 크론 작업으로 설정하여 테이블 크기를 줄일 수 있습니다.
.. 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 로그인은 기준선이 되며, 추가 검사가 없기 때문에 세 가지 방법 중 가장 빠를 것으로 예상됩니다.
defender 로그인은 아마도 Django 로그인보다 느리지만, django-axes 로그인보다는 빠를 것으로 기대합니다. 목표는 일반 원시 로그인과 defender 사이의 차이를 최소화하는 것입니다.
django-axes 로그인 속도는 아마도 세 가지 중 가장 느릴 것입니다. 더 많은 검사를 수행하고 많은 데이터베이스 쿼리를 수행하기 때문입니다.
로그인 속도를 결정하는 가장 좋은 방법은 각 설정으로 애플리케이션에 부하 테스트를 수행하고 각 유형의 로그인 시간을 비교하는 것입니다.
부하 테스트
모든 유형의 로그인을 다루기 위해 부하 테스트에서 하나 이상의 테스트가 필요합니다.
#. 모두 성공: 성공적인 로그인만으로 부하 테스트를 수행합니다.
#. 혼합: 일부 성공, 일부 실패: 성공적인 로그인과 실패를 혼합하여 부하 테스트를 수행하여 실패가 성능에 미치는 영향을 확인합니다.
#. 모두 실패: 모든 로그인이 실패하는 부하 테스트를 수행하고 성능 차이를 확인합니다.
부하 테스트에 사용할 샘플 애플리케이션이 필요하며, 유일한 차이점은 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\ : Int: 실패한 로그인에 대한 레코드가 생성되기 전에 허용되는 로그인 시도 횟수. [기본값: 3\ ]
DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME\ : Int: 사용자 이름에 대해 실패한 로그인에 대한 레코드가 생성되기 전에 허용되는 로그인 시도 횟수. [기본값: DEFENDER_LOGIN_FAILURE_LIMIT\ ]
DEFENDER_LOGIN_FAILURE_LIMIT_IP\ : Int: IP에서 실패한 로그인에 대한 레코드가 생성되기 전에 허용되는 로그인 시도 횟수. [기본값: DEFENDER_LOGIN_FAILURE_LIMIT\ ]
DEFENDER_BEHIND_REVERSE_PROXY\ : Boolean: defender가 리버스 프록시 뒤에 있습니까? [기본값: False\ ]
DEFENDER_REVERSE_PROXY_HEADER\ : String: 리버스 프록시 IP 주소가 포함된 HTTP 헤더의 이름 [기본값: HTTP_X_FORWARDED_FOR\ ]
DEFENDER_LOCK_OUT_BY_IP_AND_USERNAME\ : Boolean: 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 인증을 무차별 대입 공격으로부터 보호해야 하는 경우 사용자 정의 인증 방법을 구현할 수 있습니다.
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를 서브클래싱하는 새로운 뷰를 생성한 다음, 이 뷰들을 urls.py에서 Django 내장 뷰를 대체하여 사용해야 합니다.
뷰는 비밀번호 재설정 뷰에 제출된 이메일 주소를 기준으로 차단합니다. 이는 기본 구현(사용자 이름 사용)과 다르므로, 로그인 및 완료된 비밀번호 재설정 후에 상태를 정리하는 데 주의해야 합니다.
.. 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\ : Boolean: True이면 사용자의 IP 주소를 잠그지 않고 사용자 이름만 잠급니다. [기본값: False]
DEFENDER_DISABLE_USERNAME_LOCKOUT\ : Boolean: True이면 사용자 이름을 잠그지 않고 IP 주소만 잠급니다. [기본값: False]
DEFENDER_COOLOFF_TIME\ : Int: 설정된 경우, 이전 실패한 로그인 시도와 사용자 이름/IP 잠금이 잊혀지는 비활성 기간을 정의합니다. 정수이며 초 단위로 해석됩니다. 0이면 실패한 로그인 시도와 사용자 이름/IP 잠금이 모두 만료되지 않습니다. [기본값: 300\ ]
DEFENDER_ATTEMPT_COOLOFF_TIME\ : Int: 설정된 경우, DEFENDER_COOLOFF_TIME에 의해 설정된 이전 실패한 로그인 시도가 잊혀지는 비활성 기간을 재정의합니다. 정수이며 초 단위로 해석됩니다. 0이면 실패한 로그인 시도가 만료되지 않습니다. [기본값: DEFENDER_COOLOFF_TIME\ ]
DEFENDER_LOCKOUT_COOLOFF_TIME\ : Int 또는 List: 설정된 경우, DEFENDER_COOLOFF_TIME에 의해 설정된 사용자 이름/IP 잠금이 잊혀지는 비활성 기간을 재정의합니다. 정수이며 초 단위로 해석됩니다. 정수 목록인 경우, 마지막 DEFENDER_ACCESS_ATTEMPT_EXPIRATION 시간 동안 발생한 이전 잠금 횟수(최대값까지)의 인덱스가 초 단위를 나타냅니다. 이 속성이 0 또는 []로 설정되면 사용자 이름/IP 잠금이 만료되지 않습니다. [기본값: DEFENDER_COOLOFF_TIME\ ]
DEFENDER_LOCKOUT_TEMPLATE\ : String: [기본값: None\ ] 설정된 경우, 사용자가 잠겼을 때 렌더링할 템플릿을 지정합니다. 템플릿은 다음 컨텍스트 변수를 받습니다:
cooloff_time_seconds\ : 초 단위의 쿨오프 시간cooloff_time_minutes\ : 분 단위의 쿨오프 시간failure_limit\ : 차단되기 전의 실패 횟수DEFENDER_USERNAME_FORM_FIELD\ : String: 사용자 사용자 이름을 포함하는 양식 필드의 이름. [기본값: username\ ]
DEFENDER_CACHE_PREFIX\ : String: defender 키의 캐시 접두사. [기본값: defender\ ]
DEFENDER_LOCKOUT_URL\ : String: 누군가 잠겼을 때 리디렉션할 URL.
DEFENDER_REDIS_URL\ : String: defender의 Redis URL. [기본값: redis://localhost:6379/0\ ] (비밀번호 예시: redis://:mypassword@localhost:6379/0\ )
DEFENDER_REDIS_PASSWORD_QUOTE\ : Boolean: Redis 비밀번호에 특수 문자(예: '@')가 있는 경우, 비밀번호를 urllib.parse.quote("password!@#")로 인용하고 True로 설정할 수 있습니다. [기본값: False\ ]
DEFENDER_REDIS_NAME\ : String: Django 설정의 CACHES에서 캐시 이름 (예: "default"). 설정된 경우, DEFENDER_REDIS_URL은 무시됩니다. [기본값: None\ ]
DEFENDER_STORE_ACCESS_ATTEMPTS\ : Boolean: 로그인 시도를 데이터베이스에 저장하려면 True로 설정합니다. False이면 저장되지 않습니다. [기본값: True\ ]
DEFENDER_USE_CELERY\ : Boolean: Celery를 사용하여 로그인 시도를 데이터베이스에 저장하려면 True로 설정합니다. False이면 인라인으로 저장됩니다. [기본값: False\ ]
DEFENDER_ACCESS_ATTEMPT_EXPIRATION\ : Int: 관리 명령어가 정리하기 전에 접근 시도 레코드를 데이터베이스에 보관하는 시간(시간 단위). [기본값: 24\ ]
DEFENDER_GET_USERNAME_FROM_REQUEST_PATH\ : String: 요청에서 사용자 이름에 접근하는 함수의 가져오기 경로. 요청에서 사용자 이름에 접근하고 처리하는 사용자 정의 함수를 사용하려면 여기서 지정할 수 있습니다. [기본값: defender.utils.username_from_request\ ]