mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-10-22 08:19:04 +00:00
[Update] 优化登录失败次数限制的逻辑,并添加系统安全设置选项
This commit is contained in:
@@ -3,6 +3,7 @@ import uuid
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
@@ -17,7 +18,8 @@ from .tasks import write_login_log_async
|
||||
from .models import User, UserGroup, LoginLog
|
||||
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
|
||||
IsSuperUserOrAppUser
|
||||
from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code
|
||||
from .utils import check_user_valid, generate_token, get_login_ip, \
|
||||
check_otp_code, set_user_login_failed_count_to_cache, is_block_login
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
|
||||
@@ -149,7 +151,6 @@ class UserOtpAuthApi(APIView):
|
||||
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
|
||||
|
||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
||||
# Write login failed log
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
@@ -159,7 +160,6 @@ class UserOtpAuthApi(APIView):
|
||||
self.write_login_log(request, data)
|
||||
return Response({'msg': 'MFA认证失败'}, status=401)
|
||||
|
||||
# Write login success log
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
@@ -196,12 +196,21 @@ class UserOtpAuthApi(APIView):
|
||||
class UserAuthApi(APIView):
|
||||
permission_classes = (AllowAny,)
|
||||
serializer_class = UserSerializer
|
||||
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
||||
|
||||
def post(self, request):
|
||||
user, msg = self.check_user_valid(request)
|
||||
|
||||
username = request.data.get('username')
|
||||
ip = request.data.get('remote_addr', None)
|
||||
if not ip:
|
||||
ip = get_login_ip(request)
|
||||
key_limit = self.key_prefix_limit.format(ip, username)
|
||||
if is_block_login(key_limit):
|
||||
msg = _("Log in frequently and try again later")
|
||||
return Response({'msg': msg}, status=401)
|
||||
|
||||
if not user:
|
||||
# Write login failed log
|
||||
data = {
|
||||
'username': request.data.get('username', ''),
|
||||
'mfa': LoginLog.MFA_UNKNOWN,
|
||||
@@ -209,10 +218,11 @@ class UserAuthApi(APIView):
|
||||
'status': False
|
||||
}
|
||||
self.write_login_log(request, data)
|
||||
|
||||
set_user_login_failed_count_to_cache(key_limit)
|
||||
return Response({'msg': msg}, status=401)
|
||||
|
||||
if not user.otp_enabled:
|
||||
# Write login success log
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if login_limit %}
|
||||
{% if block_login %}
|
||||
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
|
||||
{% elif form.errors %}
|
||||
{% if 'captcha' in form.errors %}
|
||||
|
@@ -332,3 +332,29 @@ def check_password_rules(password):
|
||||
|
||||
match_obj = re.match(pattern, password)
|
||||
return bool(match_obj)
|
||||
|
||||
|
||||
def set_user_login_failed_count_to_cache(key_limit):
|
||||
count = cache.get(key_limit)
|
||||
count = count + 1 if count else 1
|
||||
|
||||
setting_limit_time = Setting.objects.filter(
|
||||
name='SECURITY_LOGIN_LIMIT_TIME'
|
||||
).first()
|
||||
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
|
||||
else settings.DEFAULT_LOGIN_LIMIT_TIME
|
||||
|
||||
cache.set(key_limit, count, int(limit_time)*60)
|
||||
|
||||
|
||||
def is_block_login(key_limit):
|
||||
count = cache.get(key_limit)
|
||||
|
||||
setting_limit_count = Setting.objects.filter(
|
||||
name='SECURITY_LOGIN_LIMIT_COUNT'
|
||||
).first()
|
||||
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
|
||||
else settings.DEFAULT_LOGIN_LIMIT_COUNT
|
||||
|
||||
if count and count >= limit_count:
|
||||
return True
|
||||
|
@@ -27,7 +27,8 @@ from common.models import Setting
|
||||
from ..models import User, LoginLog
|
||||
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \
|
||||
redirect_user_first_login_or_index, get_user_or_tmp_user, \
|
||||
set_tmp_user_to_cache, get_password_check_rules, check_password_rules
|
||||
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
|
||||
is_block_login, set_user_login_failed_count_to_cache
|
||||
from ..tasks import write_login_log_async
|
||||
from .. import forms
|
||||
|
||||
@@ -63,9 +64,9 @@ class UserLoginView(FormView):
|
||||
# limit login authentication
|
||||
ip = get_login_ip(request)
|
||||
username = self.request.POST.get('username')
|
||||
count = cache.get(self.key_prefix_limit.format(ip, username))
|
||||
if count and count >= 3:
|
||||
return self.render_to_response(self.get_context_data(login_limit=True))
|
||||
key_limit = self.key_prefix_limit.format(ip, username)
|
||||
if is_block_login(key_limit):
|
||||
return self.render_to_response(self.get_context_data(block_login=True))
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
@@ -87,13 +88,12 @@ class UserLoginView(FormView):
|
||||
}
|
||||
self.write_login_log(data)
|
||||
|
||||
# limit user login failed times
|
||||
# limit user login failed count
|
||||
ip = get_login_ip(self.request)
|
||||
key_limit = self.key_prefix_limit.format(ip, username)
|
||||
count = cache.get(key_limit)
|
||||
count = count + 1 if count else 1
|
||||
cache.set(key_limit, count, 1800)
|
||||
set_user_login_failed_count_to_cache(key_limit)
|
||||
|
||||
# show captcha
|
||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
||||
old_form = form
|
||||
form = self.form_class_captcha(data=form.data)
|
||||
|
Reference in New Issue
Block a user