eagleye

企业级审计日志 API 实现存档

企业级审计日志 API 实现存档

1. 序列化器实现

# apps/audit_logs/serializers.py

from rest_framework import serializers

from .models import SecurityEvent

from django.utils.translation import gettext_lazy as _

class SecurityEventSerializer(serializers.ModelSerializer):

event_type_display = serializers.CharField(

source='get_event_type_display',

read_only=True,

label=_("事件类型显示")

)

timestamp = serializers.DateTimeField(

format='%Y-%m-%d %H:%M:%S',

default_timezone=None,

label=_("时间戳")

)

class Meta:

model = SecurityEvent

fields = [

'id', 'timestamp', 'username', 'event_type',

'event_type_display', 'ip_address', 'user_agent', 'details'

]

read_only_fields = fields # 所有字段只读

extra_kwargs = {

'event_type': {'label': _("事件类型代码")},

'username': {'label': _("用户名")},

'ip_address': {'label': _("IP地址")},

'user_agent': {'label': _("用户代理")},

'details': {'label': _("详情")},

}

class SecurityEventExportSerializer(serializers.Serializer):

start_date = serializers.DateField(

required=True,

label=_("开始日期"),

help_text=_("导出数据的起始日期 (YYYY-MM-DD)")

)

end_date = serializers.DateField(

required=True,

label=_("结束日期"),

help_text=_("导出数据的结束日期 (YYYY-MM-DD)")

)

event_types = serializers.MultipleChoiceField(

choices=SecurityEvent.EVENT_TYPES,

required=False,

label=_("事件类型过滤"),

help_text=_("选择要导出的事件类型,不选则导出所有")

)

format = serializers.ChoiceField(

choices=[('csv', 'CSV'), ('json', 'JSON'), ('xlsx', 'Excel')],

default='csv',

label=_("导出格式")

)

2. 视图集实现

# apps/audit_logs/views.py

from rest_framework import viewsets, status, mixins

from rest_framework.response import Response

from rest_framework.decorators import action

from rest_framework.permissions import IsAuthenticated

from rest_framework.filters import SearchFilter, OrderingFilter

from django_filters.rest_framework import DjangoFilterBackend

from django.utils import timezone

from django.db.models import Q

from django.http import HttpResponse

from django.utils.translation import gettext_lazy as _

import csv

import json

import pandas as pd

from io import BytesIO

from .models import SecurityEvent

from .serializers import SecurityEventSerializer, SecurityEventExportSerializer

from apps.core.permissions import IsAuditAdmin

class SecurityEventViewSet(

mixins.ListModelMixin,

mixins.RetrieveModelMixin,

mixins.DestroyModelMixin,

viewsets.GenericViewSet

):

"""

安全审计日志API

提供安全审计日志的查看、搜索、导出和删除功能

"""

queryset = SecurityEvent.objects.all()

serializer_class = SecurityEventSerializer

permission_classes = [IsAuthenticated, IsAuditAdmin]

# 企业级过滤和排序

filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]

filterset_fields = {

'timestamp': ['gte', 'lte', 'exact', 'gt', 'lt'],

'event_type': ['exact'],

'username': ['exact', 'icontains'],

'ip_address': ['exact', 'icontains'],

}

search_fields = ['username', 'ip_address', 'user_agent', 'details', 'event_type']

ordering_fields = ['timestamp', 'username', 'event_type']

ordering = ['-timestamp'] # 默认按时间倒序

# 企业级分页配置

pagination_class = None # 默认不分页,使用自定义分页

def get_queryset(self):

"""企业级查询优化和时间范围限制"""

queryset = super().get_queryset()

# 自动过滤最近90天日志(可配置)

retention_days = 90

retention_date = timezone.now() - timezone.timedelta(days=retention_days)

queryset = queryset.filter(timestamp__gte=retention_date)

# 添加审计日志访问记录

self._log_access()

return queryset.select_related().only(

'id', 'timestamp', 'username', 'event_type',

'ip_address', 'user_agent', 'details'

)

def _log_access(self):

"""记录审计日志访问事件"""

from .models import SecurityEvent

from .signals import audit_log_access

# 触发信号记录访问事件

audit_log_access.send(

sender=self.__class__,

user=self.request.user,

view_name=self.get_view_name(),

query_params=self.request.query_params

)

@action(detail=False, methods=['post'], serializer_class=SecurityEventExportSerializer)

def export(self, request):

"""

导出审计日志

支持导出为CSV、JSON或Excel格式,可按日期范围和事件类型过滤

"""

serializer = SecurityEventExportSerializer(data=request.data)

if not serializer.is_valid():

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

data = serializer.validated_data

start_date = data['start_date']

end_date = data['end_date'] + timezone.timedelta(days=1) # 包含结束日期的全天

# 构建查询

queryset = SecurityEvent.objects.filter(

timestamp__gte=start_date,

timestamp__lte=end_date

)

# 事件类型过滤

if event_types := data.get('event_types'):

queryset = queryset.filter(event_type__in=event_types)

# 限制最大导出数量

max_export = 50000

if queryset.count() > max_export:

return Response(

{'detail': _('导出数据量过大,请缩小时间范围或添加更多过滤条件')},

status=status.HTTP_400_BAD_REQUEST

)

# 执行导出

export_format = data['format']

if export_format == 'csv':

return self._export_csv(queryset)

elif export_format == 'json':

return self._export_json(queryset)

elif export_format == 'xlsx':

return self._export_excel(queryset)

return Response(

{'detail': _('不支持的导出格式')},

status=status.HTTP_400_BAD_REQUEST

)

def _export_csv(self, queryset):

"""导出为CSV格式"""

response = HttpResponse(content_type='text/csv')

response['Content-Disposition'] = 'attachment; filename="audit_logs_export_{}.csv"'.format(

timezone.now().strftime("%Y%m%d_%H%M%S")

)

# 使用Django的CSV模块

writer = csv.writer(response)

# 写入表头

headers = [

_("ID"), _("时间戳"), _("用户名"), _("事件类型代码"),

_("事件类型"), _("IP地址"), _("用户代理"), _("详情")

]

writer.writerow(headers)

# 写入数据

for event in queryset:

writer.writerow([

event.id,

event.timestamp.strftime("%Y-%m-%d %H:%M:%S"),

event.username,

event.event_type,

event.get_event_type_display(),

event.ip_address,

event.user_agent,

event.details

])

return response

def _export_json(self, queryset):

"""导出为JSON格式"""

data = list(queryset.values(

'id', 'timestamp', 'username', 'event_type',

'ip_address', 'user_agent', 'details'

))

# 添加事件类型显示名称

for item in data:

item['event_type_display'] = dict(SecurityEvent.EVENT_TYPES).get(item['event_type'], '')

response = HttpResponse(

json.dumps(data, ensure_ascii=False, indent=2),

content_type='application/json'

)

response['Content-Disposition'] = 'attachment; filename="audit_logs_export_{}.json"'.format(

timezone.now().strftime("%Y%m%d_%H%M%S")

)

return response

def _export_excel(self, queryset):

"""导出为Excel格式"""

# 使用Pandas创建DataFrame

data = []

for event in queryset:

data.append({

'ID': event.id,

'时间戳': event.timestamp.strftime("%Y-%m-%d %H:%M:%S"),

'用户名': event.username,

'事件类型代码': event.event_type,

'事件类型': event.get_event_type_display(),

'IP地址': event.ip_address,

'用户代理': event.user_agent,

'详情': event.details

})

df = pd.DataFrame(data)

# 创建Excel文件

output = BytesIO()

with pd.ExcelWriter(output, engine='xlsxwriter') as writer:

df.to_excel(writer, sheet_name='安全审计日志', index=False)

# 获取工作簿和工作表对象进行格式设置

workbook = writer.book

worksheet = writer.sheets['安全审计日志']

# 设置列宽

worksheet.set_column('A:A', 10) # ID

worksheet.set_column('B:B', 20) # 时间戳

worksheet.set_column('C:C', 15) # 用户名

worksheet.set_column('D:D', 15) # 事件类型代码

worksheet.set_column('E:E', 20) # 事件类型

worksheet.set_column('F:F', 15) # IP地址

worksheet.set_column('G:G', 40) # 用户代理

worksheet.set_column('H:H', 60) # 详情

# 添加标题格式

header_format = workbook.add_format({

'bold': True,

'text_wrap': True,

'valign': 'top',

'fg_color': '#D7E4BC',

'border': 1

})

# 应用标题格式

for col_num, value in enumerate(df.columns.values):

worksheet.write(0, col_num, value, header_format)

# 准备HTTP响应

output.seek(0)

response = HttpResponse(

output.read(),

content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'

)

response['Content-Disposition'] = 'attachment; filename="audit_logs_export_{}.xlsx"'.format(

timezone.now().strftime("%Y%m%d_%H%M%S")

)

return response

3. 权限控制

# apps/core/permissions.py

from rest_framework.permissions import BasePermission

from django.utils.translation import gettext_lazy as _

class IsAuditAdmin(BasePermission):

"""

审计管理员权限

只有具有特定权限的用户才能访问审计日志

"""

message = _('您没有查看审计日志的权限')

def has_permission(self, request, view):

# 检查用户是否有特定权限或属于特定组

return (

request.user and

request.user.is_authenticated and

(

request.user.is_superuser or

request.user.groups.filter(name='Audit Administrators').exists()

)

)

4. 路由配置

# config/urls.py

from django.urls import path, include

from rest_framework.routers import DefaultRouter

from apps.audit_logs.views import SecurityEventViewSet

router = DefaultRouter()

router.register(r'audit-logs', SecurityEventViewSet, basename='audit-log')

urlpatterns = [

# 其他路由...

path('api/v1/', include(router.urls)),

]

5. 信号处理(可选)

# apps/audit_logs/signals.py

from django.dispatch import receiver

from django.db.models.signals import post_save, pre_delete

from .models import SecurityEvent

from apps.audit_logs.models import AuditLogAccess

@receiver(post_save, sender=SecurityEvent)

def log_security_event_creation(sender, instance, created, **kwargs):

"""

记录审计日志条目的创建或更新

"""

if created:

# 记录新审计事件创建

pass

else:

# 记录审计事件更新(通常审计日志不应该被更新)

pass

@receiver(pre_delete, sender=SecurityEvent)

def log_security_event_deletion(sender, instance, **kwargs):

"""

记录审计日志条目的删除

"""

# 在实际删除前记录删除操作

pass

def audit_log_access(sender, **kwargs):

"""

记录审计日志访问事件

"""

user = kwargs.get('user')

view_name = kwargs.get('view_name')

query_params = kwargs.get('query_params')

AuditLogAccess.objects.create(

user=user,

view_name=view_name,

query_params=str(query_params)

)

6. 企业级配置增强

自定义分页类

# core/pagination.py

from rest_framework.pagination import PageNumberPagination

from rest_framework.response import Response

class EnterprisePageNumberPagination(PageNumberPagination):

"""企业级分页配置"""

page_size = 50

page_size_query_param = 'page_size'

max_page_size = 500

def get_paginated_response(self, data):

return Response({

'links': {

'next': self.get_next_link(),

'previous': self.get_previous_link()

},

'count': self.page.paginator.count,

'page_size': self.get_page_size(self.request),

'current_page': self.page.number,

'total_pages': self.page.paginator.num_pages,

'results': data

})

视图集分页配置

# 在SecurityEventViewSet中添加

from core.pagination import EnterprisePageNumberPagination

class SecurityEventViewSet(...):

pagination_class = EnterprisePageNumberPagination

def get_paginated_response(self, data):

# 添加自定义元数据

response = super().get_paginated_response(data)

response.data['filters'] = self.request.query_params.dict()

response.data['event_types'] = dict(SecurityEvent.EVENT_TYPES)

return response

7. 使用示例

获取审计日志列表

GET /api/v1/audit-logs/

参数:

  • ordering: 排序字段(如timestamp,-timestamp)
  • event_type: 过滤事件类型(如LOGIN_SUCCESS)
  • username: 过滤用户名
  • timestamp__gte: 过滤起始时间(ISO格式)
  • timestamp__lte: 过滤结束时间(ISO格式)
  • page: 页码
  • page_size: 每页数量

获取单个审计日志详情

GET /api/v1/audit-logs/{id}/

导出审计日志

POST /api/v1/audit-logs/export/

请求体:

{

"start_date": "2025-07-01",

"end_date": "2025-07-10",

"event_types": ["LOGIN_SUCCESS", "LOGIN_FAILED"],

"format": "xlsx"

}

删除审计日志

DELETE /api/v1/audit-logs/{id}/

8. 企业级特性总结

严格权限控制

  • 仅允许授权用户(审计管理员)访问
  • 自定义权限类IsAuditAdmin
  • 时间范围过滤(支持精确和范围查询)
  • 事件类型过滤
  • 用户名/IP地址搜索
  • 多字段排序
  • 支持 CSV、JSON、Excel 格式
  • 按日期范围和事件类型过滤导出数据
  • 导出数据量限制(防止资源耗尽)
  • 格式化的 Excel 输出
  • 自动限制查询时间范围(保留策略)
  • 字段选择优化(only()减少数据传输)
  • 分页支持(防止大数据集导致性能问题)
  • 记录对审计日志本身的访问
  • 防止修改历史记录(只读字段)
  • 删除操作记录(通过信号)
  • 使用gettext_lazy支持多语言
  • 字段标签和帮助文本国际化
  • 详细的字段元数据
  • 符合 OpenAPI 规范的响应结构
  • 清晰的错误消息
  • 只读API(防止篡改审计记录)
  • 敏感数据保护(无用户密码等敏感信息)
  • 访问控制日志

高级过滤功能

数据导出功能

性能优化

审计完整性

国际化支持

API 文档友好

安全增强

 

posted on 2025-07-14 16:08  GoGrid  阅读(15)  评论(0)    收藏  举报

导航