Compare commits

..

2 Commits

Author SHA1 Message Date
wangruidong
32481278ca perf: Simplify login and MFA limit key templates by removing IP address 2025-12-25 14:56:34 +08:00
feng
95aa50ed4f perf: Top session asset user cache 2025-12-23 18:02:32 +08:00
4 changed files with 24 additions and 47 deletions

View File

@@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
from rest_framework.throttling import SimpleRateThrottle
class RateThrottle(SimpleRateThrottle):
def __init__(self):
# Override the usual SimpleRateThrottle, because we can't determine
# the rate until called by the view.
pass
def allow_request(self, request, view):
if getattr(request, "user", None) and request.user.is_authenticated:
if getattr(request.user, "is_service_account", False):
self.scope = "service_account"
else:
self.scope = "user"
else:
self.scope = "anon"
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
return super().allow_request(request, view)
def get_cache_key(self, request, view):
if request.user and request.user.is_authenticated:
ident = request.user.pk
else:
ident = self.get_ident(request)
return self.cache_format % {
'scope': self.scope,
'ident': ident
}

View File

@@ -1,5 +1,6 @@
from collections import defaultdict from collections import defaultdict
from django.core.cache import cache
from django.db.models import Count, Max, F, CharField, Q from django.db.models import Count, Max, F, CharField, Q
from django.db.models.functions import Cast from django.db.models.functions import Cast
from django.http.response import JsonResponse from django.http.response import JsonResponse
@@ -144,6 +145,7 @@ class DateTimeMixin:
class DatesLoginMetricMixin: class DatesLoginMetricMixin:
days: int
dates_list: list dates_list: list
date_start_end: tuple date_start_end: tuple
command_type_queryset_list: list command_type_queryset_list: list
@@ -155,6 +157,8 @@ class DatesLoginMetricMixin:
operate_logs_queryset: OperateLog.objects operate_logs_queryset: OperateLog.objects
password_change_logs_queryset: PasswordChangeLog.objects password_change_logs_queryset: PasswordChangeLog.objects
CACHE_TIMEOUT = 60
@lazyproperty @lazyproperty
def get_type_to_assets(self): def get_type_to_assets(self):
result = Asset.objects.annotate(type=F('platform__type')). \ result = Asset.objects.annotate(type=F('platform__type')). \
@@ -214,19 +218,34 @@ class DatesLoginMetricMixin:
return date_metrics_dict.get('id', []) return date_metrics_dict.get('id', [])
def get_dates_login_times_assets(self): def get_dates_login_times_assets(self):
cache_key = f"stats:top10_assets:{self.days}"
data = cache.get(cache_key)
if data is not None:
return data
assets = self.sessions_queryset.values("asset") \ assets = self.sessions_queryset.values("asset") \
.annotate(total=Count("asset")) \ .annotate(total=Count("asset")) \
.annotate(last=Cast(Max("date_start"), output_field=CharField())) \ .annotate(last=Cast(Max("date_start"), output_field=CharField())) \
.order_by("-total") .order_by("-total")
return list(assets[:10])
result = list(assets[:10])
cache.set(cache_key, result, self.CACHE_TIMEOUT)
return result
def get_dates_login_times_users(self): def get_dates_login_times_users(self):
cache_key = f"stats:top10_users:{self.days}"
data = cache.get(cache_key)
if data is not None:
return data
users = self.sessions_queryset.values("user_id") \ users = self.sessions_queryset.values("user_id") \
.annotate(total=Count("user_id")) \ .annotate(total=Count("user_id")) \
.annotate(user=Max('user')) \ .annotate(user=Max('user')) \
.annotate(last=Cast(Max("date_start"), output_field=CharField())) \ .annotate(last=Cast(Max("date_start"), output_field=CharField())) \
.order_by("-total") .order_by("-total")
return list(users[:10]) result = list(users[:10])
cache.set(cache_key, result, self.CACHE_TIMEOUT)
return result
def get_dates_login_record_sessions(self): def get_dates_login_record_sessions(self):
sessions = self.sessions_queryset.order_by('-date_start') sessions = self.sessions_queryset.order_by('-date_start')

View File

@@ -38,14 +38,6 @@ REST_FRAMEWORK = {
"oauth2_provider.contrib.rest_framework.OAuth2Authentication", "oauth2_provider.contrib.rest_framework.OAuth2Authentication",
'authentication.backends.drf.SessionAuthentication', 'authentication.backends.drf.SessionAuthentication',
), ),
'DEFAULT_THROTTLE_CLASSES': (
'common.drf.throttling.RateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'anon': '60/min',
'user': '180/min',
'service_account': '300/min',
},
'DEFAULT_FILTER_BACKENDS': ( 'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend', 'django_filters.rest_framework.DjangoFilterBackend',
'common.drf.filters.SearchFilter', 'common.drf.filters.SearchFilter',

View File

@@ -139,7 +139,7 @@ class BlockUtilBase:
username = username.lower() if username else '' username = username.lower() if username else ''
self.username = username self.username = username
self.ip = ip self.ip = ip
self.limit_key = self.LIMIT_KEY_TMPL.format(username, ip) self.limit_key = self.LIMIT_KEY_TMPL.format(username)
self.block_key = self.BLOCK_KEY_TMPL.format(username) self.block_key = self.BLOCK_KEY_TMPL.format(username)
self.key_ttl = int(settings.SECURITY_LOGIN_LIMIT_TIME) * 60 self.key_ttl = int(settings.SECURITY_LOGIN_LIMIT_TIME) * 60
@@ -236,12 +236,12 @@ class BlockGlobalIpUtilBase:
class LoginBlockUtil(BlockUtilBase): class LoginBlockUtil(BlockUtilBase):
LIMIT_KEY_TMPL = "_LOGIN_LIMIT_{}_{}" LIMIT_KEY_TMPL = "_LOGIN_LIMIT_{}"
BLOCK_KEY_TMPL = "_LOGIN_BLOCK_{}" BLOCK_KEY_TMPL = "_LOGIN_BLOCK_{}"
class MFABlockUtils(BlockUtilBase): class MFABlockUtils(BlockUtilBase):
LIMIT_KEY_TMPL = "_MFA_LIMIT_{}_{}" LIMIT_KEY_TMPL = "_MFA_LIMIT_{}"
BLOCK_KEY_TMPL = "_MFA_BLOCK_{}" BLOCK_KEY_TMPL = "_MFA_BLOCK_{}"