From 086ecfc046375341493be9423e3cca1f65196aea Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 18 Nov 2021 16:55:23 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=85=A8=E5=B1=80ip?= =?UTF-8?q?=E9=99=90=E5=88=B6=E9=80=BB=E8=BE=91=20(#7220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化全局ip限制逻辑 * perf: 优化全局ip限制逻辑 2 * perf: 优化全局ip限制逻辑 3 Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: Michael Bai --- apps/authentication/errors.py | 23 +++++++++++++---------- apps/authentication/mixins.py | 2 ++ apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- apps/settings/serializers/security.py | 2 +- apps/users/utils.py | 25 ++++++++++++++++++------- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index 015ca19ad..abd78d9eb 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -7,7 +7,7 @@ from rest_framework import status from common.exceptions import JMSException from .signals import post_auth_failed -from users.utils import LoginBlockUtil, MFABlockUtils +from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil reason_password_failed = 'password_failed' reason_password_decrypt_failed = 'password_decrypt_failed' @@ -114,7 +114,18 @@ class AuthFailedError(Exception): return str(self.msg) -class CredentialError(AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, AuthFailedError): +class BlockGlobalIpLoginError(AuthFailedError): + error = 'block_global_ip_login' + + def __init__(self, username, ip, **kwargs): + self.msg = _("IP is not allowed") + LoginIpBlockUtil(ip).set_block_if_need() + super().__init__(username=username, ip=ip, **kwargs) + + +class CredentialError( + AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, BlockGlobalIpLoginError, AuthFailedError +): def __init__(self, error, username, ip, request): super().__init__(error=error, username=username, ip=ip, request=request) util = LoginBlockUtil(username, ip) @@ -177,14 +188,6 @@ class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError): super().__init__(username=username, ip=ip) -class BlockGlobalIpLoginError(AuthFailedError): - error = 'block_global_ip_login' - - def __init__(self, username, ip): - self.msg = _("IP is not allowed") - super().__init__(username=username, ip=ip) - - class SessionEmptyError(AuthFailedError): msg = session_empty_msg error = 'session_empty' diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 4d70439f4..7491b3b9e 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -492,6 +492,7 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost # 标记密码验证成功 self.mark_password_ok(user=user, auto_login=auto_login) LoginBlockUtil(user.username, ip).clean_failed_count() + LoginIpBlockUtil(ip).clean_block_if_need() return user def mark_password_ok(self, user, auto_login=False): @@ -517,6 +518,7 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost self._check_login_acl(user, ip) LoginBlockUtil(user.username, ip).clean_failed_count() + LoginIpBlockUtil(ip).clean_block_if_need() MFABlockUtils(user.username, ip).clean_failed_count() self.mark_password_ok(user, False) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 59c2bf499..012f71479 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -3353,8 +3353,8 @@ msgid "" msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" #: settings/serializers/security.py:61 -msgid "IP Black List" -msgstr "IP 黑名单" +msgid "Login IP Black List" +msgstr "登录 IP 黑名单" #: settings/serializers/security.py:64 msgid "" diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py index 70b015948..b8c9e6de6 100644 --- a/apps/settings/serializers/security.py +++ b/apps/settings/serializers/security.py @@ -58,7 +58,7 @@ class SecurityAuthSerializer(serializers.Serializer): ) ) SECURITY_LOGIN_IP_BLACK_LIST = serializers.ListField( - default=[], label=_('IP Black List'), allow_empty=True, + default=[], label=_('Login IP Black List'), allow_empty=True, child=serializers.CharField(max_length=1024, validators=[ip_child_validator]), help_text=_( 'Format for comma-delimited string. Such as: ' diff --git a/apps/users/utils.py b/apps/users/utils.py index 033bc1081..370240d75 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -182,21 +182,32 @@ class BlockGlobalIpUtilBase: self.block_key = self.BLOCK_KEY_TMPL.format(ip) self.key_ttl = int(settings.SECURITY_LOGIN_LIMIT_TIME) * 60 - def sign_limit_key_and_block_key(self): + @property + def ip_in_black_list(self): + return self.ip in settings.SECURITY_LOGIN_IP_BLACK_LIST + + def set_block_if_need(self): + if not self.ip_in_black_list: + return count = cache.get(self.limit_key, 0) count += 1 cache.set(self.limit_key, count, self.key_ttl) limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT - if count >= limit_count: - cache.set(self.block_key, True, self.key_ttl) + if count < limit_count: + return + cache.set(self.block_key, True, self.key_ttl) + + def clean_block_if_need(self): + if not self.ip_in_black_list: + return + cache.delete(self.limit_key) + cache.delete(self.block_key) def is_block(self): - if self.ip in settings.SECURITY_LOGIN_IP_BLACK_LIST: - self.sign_limit_key_and_block_key() - return bool(cache.get(self.block_key)) - else: + if not self.ip_in_black_list: return False + return bool(cache.get(self.block_key)) class LoginBlockUtil(BlockUtilBase):