feat(#95): 安全模块 - RBAC角色权限+JWT认证+AES-256-GCM数据加密+安全审计哈希链+安全中间件(21个单元测试)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""安全审计:操作日志记录、查询与不可篡改链式校验。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuditEntry:
|
||||
action: str # 操作类型,如 user.create / billing.refund
|
||||
actor: str # 操作人
|
||||
target: str = "" # 操作对象
|
||||
detail: str = "" # 附加说明
|
||||
ip: str = ""
|
||||
entry_id: str = field(default_factory=lambda: uuid.uuid4().hex)
|
||||
ts: float = field(default_factory=time.time)
|
||||
prev_hash: str = ""
|
||||
entry_hash: str = ""
|
||||
|
||||
def canonical(self) -> str:
|
||||
d = asdict(self)
|
||||
d.pop("entry_hash", None)
|
||||
return json.dumps(d, sort_keys=True, ensure_ascii=False)
|
||||
|
||||
|
||||
class AuditLogger:
|
||||
"""线程安全的内存审计日志(哈希链防篡改),生产可替换为 DB 存储。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._entries: list[AuditEntry] = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def log(self, action: str, actor: str, target: str = "", detail: str = "", ip: str = "") -> AuditEntry:
|
||||
with self._lock:
|
||||
prev = self._entries[-1].entry_hash if self._entries else "GENESIS"
|
||||
entry = AuditEntry(action=action, actor=actor, target=target, detail=detail, ip=ip,
|
||||
prev_hash=prev)
|
||||
entry.entry_hash = hashlib.sha256(entry.canonical().encode("utf-8")).hexdigest()
|
||||
self._entries.append(entry)
|
||||
return entry
|
||||
|
||||
def query(self, *, actor: Optional[str] = None, action: Optional[str] = None,
|
||||
since: Optional[float] = None, until: Optional[float] = None) -> list[AuditEntry]:
|
||||
with self._lock:
|
||||
result = list(self._entries)
|
||||
if actor is not None:
|
||||
result = [e for e in result if e.actor == actor]
|
||||
if action is not None:
|
||||
result = [e for e in result if e.action == action]
|
||||
if since is not None:
|
||||
result = [e for e in result if e.ts >= since]
|
||||
if until is not None:
|
||||
result = [e for e in result if e.ts <= until]
|
||||
return result
|
||||
|
||||
def verify_chain(self) -> bool:
|
||||
"""校验哈希链完整性;任何条目被篡改都会返回 False"""
|
||||
with self._lock:
|
||||
entries = list(self._entries)
|
||||
prev = "GENESIS"
|
||||
for e in entries:
|
||||
if e.prev_hash != prev:
|
||||
return False
|
||||
if hashlib.sha256(e.canonical().encode("utf-8")).hexdigest() != e.entry_hash:
|
||||
return False
|
||||
prev = e.entry_hash
|
||||
return True
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._entries)
|
||||
Reference in New Issue
Block a user