Entity 歷程稽核
Entity 歷程稽核用來回答:「哪一個 changeset 改了哪些 Entity、修改前後是什麼,以及當時的 subject、actor、delegation grantors 是誰?」目前的 reference adapter 使用 Hibernate Envers, 但身份脈絡契約不依賴 Envers。
三個互相獨立的稽核層
| 層次 | 解決的問題 | 啟用方式 | 是否包含 acting 維度 |
|---|---|---|---|
AuditableBase | 目前這列由誰建立、最後由誰修改 | Entity 選擇繼承 | 否,只存有效身份 subject |
| Entity history | 每次交易改了什麼、修改前後版本與 changeset metadata | app 安裝 feature,Entity 再加 @Audited | 是,revision 記 subject/actor/grantors |
AuditEvent | 登入、授權、acting 存取、HTTP 結果等安全/操作事件 | audit 與各事件發布點分別選配 | 事件 payload 依事件類型決定 |
三層沒有 prerequisite 關係。安裝 Entity history 不會改寫 AuditableBase,也不會自動 audit
所有 Entity;沒有安裝 impersonation 或 delegation 時,metadata 會自然退化為
subject = actor、grantors = []。
身份語意
所有身份欄都保存 stable subject(目前通常是 Account UUID 字串),不是 username 或顯示名稱。 顯示時才透過應用的 principal directory 解析。
| 情境 | subjectId | actorId | grantorIds |
|---|---|---|---|
| 一般操作 | 登入者 | 登入者 | 空集合 |
| Impersonation | 被模擬者 | 真實操作者 | 空集合 |
| Delegation | 登入者 | 登入者 | 權限授權人集合 |
| 兩者並存 | 被模擬者 | 真實操作者 | 權限授權人集合 |
AuditChangeContext 另外承載 principalKind、可選 tenantId、requestId、sessionId 與
source。SecurityAuditChangeContextProvider 從 Spring Security、ActingContext 與當前
HTTP request 解算這些值;非 HTTP 的已認證工作標為 APPLICATION,沒有 Authentication 的
工作以 system 主體記錄(非 HTTP 來源為 SYSTEM,HTTP 來源仍為 HTTP)。
外部 request/correlation ID 超過 reference schema 的 100 字元上限時,provider 會保存固定長度
SHA-256 摘要,避免不受信任標頭使 audit transaction 寫入失敗。
啟用方式
Entity history 是 application-level optional feature。reference app 的
gradle/features/entity-audit.gradle.kts 才加入 runtime adapter 相依:
dependencies {
add("implementation", "org.hibernate.orm:hibernate-envers")
}
框架的 Envers adapter 是 compileOnly,所以沒有安裝此 feature 的應用不會被迫帶入 Envers。
移除 feature 時,應一併移除 feature catalog 宣告、Gradle seam、revision entity/listener 與
相關測試支援。
安裝 feature 後,仍須逐一選擇要保留歷程的 Entity:
@Entity
@Audited
public class PurchaseOrder extends AuditableBase {
// ...
}
沒有 @Audited 的 production Entity 不會建立 _AUD 歷程表,也不會因 feature 存在而被納入。
框架與應用的責任
框架提供兩層窄契約:
AuditChangeContext/AuditChangeContextProvider/AuditChangeContextAware:與 history engine 無關的 changeset metadata。EnversAuditRevisionListener:把上述 context 寫入 application-owned revision entity 的 Envers adapter。
應用擁有具體 @RevisionEntity、資料表、索引、欄位長度與 tenant 投影。tenant reference app
用 listener 子類注入 TenantContext::getTenantIdOrNull;tenantless variant 使用中立 provider,
其 audit_revision 不含 tenant_id。
Reference schema 使用:
audit_revision:一筆代表同一個持久化 transaction 的 changeset,保存 subject、actor、 principal kind、request/session/source,以及 tenant app 的 tenant。audit_revision_grantor:以(REV, grantor_order)保存 0..n 個授權人,不把多值身份塞進 JSON 或逗號字串。<entity>_AUD:Envers 保存 Entity 各版本,透過REV指回 changeset metadata。
同一個 transaction 修改多個 audited Entity 時只產生一筆 revision;因此 actor/grantors 是
changeset metadata,不需要重複塞進每個業務 Entity 或 AuditableBase。
查詢與交易邊界
Revision 在 audited transaction commit 時完成。在同一個尚未提交的 transaction 內,以
AuditReader 查不到該次 revision 是正常行為;查詢與驗證應跨過 commit boundary。
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisions = reader.getRevisions(PurchaseOrder.class, orderId);
AuditRevision metadata = reader.findRevision(AuditRevision.class, revisions.getLast());
Envers 攔截 Hibernate Entity lifecycle。bulk HQL/JPQL update、native SQL、資料庫端程序或其他 繞過一般 Entity lifecycle 的寫入,不應假設會產生完整歷程;使用前須以實際 persistence 路徑做整合測試,或改由 application service 明確發布另一種 audit record。
Adapter 演進
公開身份契約與應用 schema 不直接依賴 Envers listener API;只有
audit.entity.envers adapter 與 application revision wiring 知道 Envers。升級 Hibernate 或
改用其他 history engine 時,先替換 adapter 並驗證既有 REV 關聯與查詢相容性,不需要把
impersonation/delegation 欄位搬進所有 Entity。