企业级审计日志 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 文档友好
安全增强
浙公网安备 33010602011771号