<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Asp-Net-Core開發(fā)筆記:實(shí)現(xiàn)動(dòng)態(tài)審計(jì)日志功能

          共 13471字,需瀏覽 27分鐘

           ·

          2024-04-11 20:12

          前言

          最近一直在寫 Go 和 Python ,好久沒寫 C# ,重新回來寫 C# 代碼時(shí)竟有一種親切感~

          說回正題。

          在當(dāng)今這個(gè)數(shù)字化迅速發(fā)展的時(shí)代,每一個(gè)操作都可能對(duì)業(yè)務(wù)產(chǎn)生深遠(yuǎn)的影響,無論是對(duì)數(shù)據(jù)的簡(jiǎn)單查詢,還是對(duì)系統(tǒng)配置的修改。在這樣的背景下,審計(jì)日志不僅僅是一種遵循最佳實(shí)踐的手段,更是確保數(shù)據(jù)安全、提高系統(tǒng)透明度、促進(jìn)責(zé)任歸屬明晰的關(guān)鍵工具。通過詳細(xì)記錄誰在何時(shí)對(duì)系統(tǒng)進(jìn)行了何種操作,審計(jì)日志幫助組織追蹤用戶活動(dòng),分析系統(tǒng)問題,甚至在發(fā)生安全事件時(shí),提供必要的線索進(jìn)行調(diào)查。

          實(shí)現(xiàn)審計(jì)日志的方法多樣,但如何在不干擾主業(yè)務(wù)邏輯的同時(shí),高效地集成這一功能,是開發(fā)者們面臨的一大挑戰(zhàn)。本文著重探討如何借鑒面向切面編程(Aspect-Oriented Programming, AOP)的設(shè)計(jì)思想,在ASP.NET Core應(yīng)用中以最小化代碼侵入性實(shí)現(xiàn)動(dòng)態(tài)審計(jì)日志功能。AOP允許我們通過預(yù)定義的模式,如日志記錄、性能統(tǒng)計(jì)和安全控制,以聲明的方式增強(qiáng)代碼功能,而無需修改實(shí)際的業(yè)務(wù)邏輯代碼。

          本文將指導(dǎo)讀者從概念的理解到具體的實(shí)施,再到最終的數(shù)據(jù)持久化處理,特別是如何利用MongoDB這一強(qiáng)大的NoSQL數(shù)據(jù)庫來持久化審計(jì)日志數(shù)據(jù)。無論你是剛剛接觸ASP.NET Core的新手,還是尋求為現(xiàn)有項(xiàng)目增加審計(jì)功能的資深開發(fā)者,本文都將提供從理論到實(shí)踐的全面指導(dǎo)。通過本文,你將學(xué)習(xí)到如何設(shè)計(jì)和實(shí)現(xiàn)一個(gè)靈活、可擴(kuò)展的審計(jì)日志系統(tǒng),同時(shí)保持對(duì)主業(yè)務(wù)邏輯的最小化干擾。

          讓我們開始這一旅程,一步步探索如何在ASP.NET Core應(yīng)用中集成高效、靈活的審計(jì)日志機(jī)制,利用AOP設(shè)計(jì)思想實(shí)現(xiàn)高度解耦和動(dòng)態(tài)增強(qiáng)的系統(tǒng)功能。

          審計(jì)日志基礎(chǔ)

          定義和用途

          審計(jì)日志有助于追蹤用戶的操作行為、數(shù)據(jù)變更記錄以及系統(tǒng)的安全性分析等。

          常用的審計(jì)日志有這些類型。

          • 操作審計(jì):記錄用戶對(duì)系統(tǒng)的所有操作,例如登錄、登出、數(shù)據(jù)增刪改查等。
          • 數(shù)據(jù)審計(jì):記錄數(shù)據(jù)的變更詳情,如記錄數(shù)據(jù)修改前后的值。
          • 安全審計(jì):記錄安全相關(guān)事件,如失敗的登錄嘗試、權(quán)限變更等。
          • 性能審計(jì):記錄關(guān)鍵操作的性能數(shù)據(jù),幫助分析系統(tǒng)瓶頸。

          本文的代碼以實(shí)現(xiàn)操作審計(jì)為例。

          模型定義&關(guān)鍵信息

          審計(jì)日志是系統(tǒng)安全和管理的關(guān)鍵部分,它幫助我們理解系統(tǒng)內(nèi)發(fā)生了什么、何時(shí)發(fā)生、由誰觸發(fā)。為了實(shí)現(xiàn)這一目標(biāo),審計(jì)日志記錄需要包含幾個(gè)關(guān)鍵的組成部分。

          • EventId 是每條審計(jì)記錄的唯一標(biāo)識(shí)符。就像每個(gè)人都有一個(gè)獨(dú)一無二的身份證號(hào)一樣,每條審計(jì)日志也有一個(gè)獨(dú)特的EventId。這使我們能夠輕松地找到和引用特定的審計(jì)事件。
          • EventType 描述了發(fā)生的事件類型。這告訴我們這條記錄是關(guān)于什么的——是用戶登錄、數(shù)據(jù)修改,還是權(quán)限更改等。通過查看EventType,我們可以快速了解記錄的核心信息,而無需深入研究細(xì)節(jié)。
          • UserId 是觸發(fā)事件的用戶的標(biāo)識(shí)。在審計(jì)日志中記錄UserId非常重要,因?yàn)樗鼛椭覀冏粉櫿l負(fù)責(zé)了什么操作。如果發(fā)現(xiàn)了問題或者不當(dāng)行為,我們可以通過UserId來確定責(zé)任人。

          設(shè)計(jì)審計(jì)日志模型

          AuditLog 類

          新建 AuditLog.cs 類,每個(gè)字段都有注釋,我就不再贅述了。

                
                public class AuditLog {
            /// <summary>
            /// 事件唯一標(biāo)識(shí)
            /// </summary>
            public string EventId { getset; }

            /// <summary>
            /// 事件類型(例如:登錄、登出、數(shù)據(jù)修改等)
            /// </summary>
            public string EventType { getset; }

            /// <summary>
            /// 執(zhí)行操作的用戶標(biāo)識(shí)
            /// </summary>
            public string UserId { getset; }

            /// <summary>
            /// 執(zhí)行操作的用戶名
            /// </summary>
            public string Username { getset; }

            /// <summary>
            /// 事件發(fā)生的時(shí)間戳
            /// </summary>
            public DateTime Timestamp { getset; }

            /// <summary>
            /// 用戶的IP地址
            /// </summary>
            public string? IPAddress { getset; }

            /// <summary>
            /// 被操作的實(shí)體名稱
            /// </summary>
            public string EntityName { getset; }

            /// <summary>
            /// 被操作的實(shí)體標(biāo)識(shí)
            /// </summary>
            public string EntityId { getset; }

            /// <summary>
            /// 修改前的數(shù)據(jù),可根據(jù)實(shí)際情況以JSON格式存儲(chǔ)
            /// </summary>
            public string? OriginalValues { getset; }

            /// <summary>
            /// 修改后的數(shù)據(jù),可根據(jù)實(shí)際情況以JSON格式存儲(chǔ)
            /// </summary>
            public string? CurrentValues { getset; }

            /// <summary>
            /// 具體的更改內(nèi)容,可根據(jù)實(shí)際情況以JSON格式存儲(chǔ)
            /// </summary>
            public string? Changes { getset; }

            /// <summary>
            /// 事件描述
            /// </summary>
            public string? Description { getset; }
          }

          捕獲審計(jì)日志

          IAuditLogService 接口

          先寫一個(gè)接口,用來操作審計(jì)日志。使用接口可以保持代碼的整潔和重用,同時(shí)也便于將來對(duì)審計(jì)日志記錄邏輯進(jìn)行擴(kuò)展或修改。

          為了簡(jiǎn)單起見,目前這里我們只寫了一個(gè)記錄的方法。

                
                public interface IAuditLogService {
            Task LogAsync(AuditLog auditLog);
          }

          之后在依賴注入容器里注冊(cè)(假設(shè)實(shí)現(xiàn)類的名稱為 AuditLogService

                
                builder.Services.AddScope<IAuditLogService, AuditLogService>();

          這個(gè)設(shè)計(jì)既保持了代碼的清晰與簡(jiǎn)潔,也為將來可能的需求變更(如改變審計(jì)日志的存儲(chǔ)方式、增加審計(jì)字段等)提供了足夠的靈活性。

          具體實(shí)現(xiàn)會(huì)在后續(xù)的數(shù)據(jù)持久化部分介紹。

          ActionFilter 方式

          在ASP.NET Core中,Action過濾器提供了一種強(qiáng)大的機(jī)制,允許我們?cè)诳刂破鞯膭?dòng)作執(zhí)行前后插入自定義邏輯。

          我們可以在不修改現(xiàn)有業(yè)務(wù)邏輯代碼的情況下,自動(dòng)地捕獲用戶的操作以及數(shù)據(jù)的更改。這種方式充分利用了AOP的思想,實(shí)現(xiàn)了代碼的最小化侵入。

          創(chuàng)建 AuditLogAttribute

          直接上代碼了,繼承自 ActionFilterAttribute 類,可以實(shí)現(xiàn)一個(gè) Action 過濾器的特性,其中 EventTypeEntityName 我設(shè)計(jì)成需要手動(dòng)指定,其他的屬性可以通過各種方法來獲取。

                
                public class AuditLogAttribute : ActionFilterAttribute {
            public string EventType { getset; }
            public string EntityName { getset; }

            public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
              var sp = context.HttpContext.RequestServices;
              var ctxItems = context.HttpContext.Items;

              try {
                var authService = sp.GetRequiredService<AuthService>();

                // 在操作執(zhí)行前
                var executedContext = await next();

                // 在操作執(zhí)行后

                // 獲取當(dāng)前用戶的身份信息
                var user = await authService.GetUserFromJwt(executedContext.HttpContext.User);

                // 構(gòu)造AuditLog對(duì)象
                var auditLog = new AuditLog {
                  EventId = Guid.NewGuid().ToString(),
                  EventType = this.EventType,
                  UserId = user.UserId,
                  Username = user.Username,
                  Timestamp = DateTime.UtcNow,
                  IPAddress = GetIpAddress(executedContext.HttpContext),
                  EntityName = this.EntityName,
                  EntityId = ctxItems["AuditLog_EntityId"]?.ToString() ?? "",
                  OriginalValues = ctxItems["AuditLog_OriginalValues"]?.ToString(),
                  CurrentValues = ctxItems["AuditLog_CurrentValues"]?.ToString(),
                  Changes = ctxItems["AuditLog_Changes"]?.ToString(),
                  Description = $"操作類型:{this.EventType},實(shí)體名稱:{this.EntityName}",
                };

                var auditService = sp.GetRequiredService<IAuditLogService>();
                await auditService.LogAsync(auditLog);
              } catch (Exception ex) {
                var logger = sp.GetRequiredService<ILogger<AuditLogAttribute>>();
                logger.LogError(ex, "An error occurred while logging audit information.");
              }
            }
          }
          注意事項(xiàng)
          • 異常處理:考慮到日志記錄不應(yīng)影響主要業(yè)務(wù)流程的執(zhí)行,需要添加異常處理邏輯,確保即使日志記錄過程中發(fā)生異常,也不會(huì)干擾到正常的業(yè)務(wù)邏輯。
          • 性能問題:雖然已經(jīng)在異步方法中記錄審計(jì)日志,但如果審計(jì)日志的記錄過程很慢,可能會(huì)略微延遲響應(yīng)時(shí)間。可以使用批處理、緩存來異步寫入數(shù)據(jù)庫,或者將記錄邏輯放到后臺(tái)任務(wù)、消息隊(duì)列中。

          獲取IP地址

          通過HttpContext.Connection.RemoteIpAddress屬性可以獲取 IP 地址,但如果應(yīng)用部署在了代理服務(wù)器后面(例如使用了負(fù)載均衡器),直接獲取的IP地址可能是代理服務(wù)器的地址,而不是客戶端的真實(shí)IP地址。

          所以這里我封裝了 GetIpAddress 方法

                
                private string? GetIpAddress(HttpContext httpContext) {
            // 首先檢查X-Forwarded-For頭(當(dāng)應(yīng)用部署在代理后面時(shí))
            var forwardedFor = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(forwardedFor)) {
              return forwardedFor.Split(',').FirstOrDefault(); // 可能包含多個(gè)IP地址
            }

            // 如果沒有X-Forwarded-For頭,或者需要直接獲取連接的遠(yuǎn)程IP地址
            return httpContext.Connection.RemoteIpAddress?.ToString();
          }

          首先嘗試從X-Forwarded-For請(qǐng)求頭中獲取IP地址,這是一個(gè)標(biāo)準(zhǔn)的HTTP頭,用于識(shí)別通過HTTP代理或負(fù)載均衡器發(fā)送請(qǐng)求的客戶端的原始IP地址。如果請(qǐng)求沒有經(jīng)過代理,或者想要獲取代理服務(wù)器的地址,那么它會(huì)回退到使用HttpContext.Connection.RemoteIpAddress

          X-Forwarded-For可能包含多個(gè)IP地址(如果請(qǐng)求通過多個(gè)代理傳遞),因此代碼中使用了Split(',')來處理這種情況,并且僅取第一個(gè)IP地址作為客戶端的真實(shí)IP地址。

          使用方法

          經(jīng)過封裝后可以很方便的使用這個(gè)審計(jì)功能了,只需要在接口上添加一行代碼就可以實(shí)現(xiàn)審計(jì)功能。

                
                [AuditLog(EventType = nameof(SetSubTaskFeedback), EntityName = nameof(SubTask))]
          [HttpPost("sub-tasks/{subId}/set-feedback")]
          public async Task<ApiResponse> SetSubTaskFeedback(string subId, [FromBody] SubTaskFeedbackDto dto) {}

          手動(dòng)記錄方式

          盡管使用Action過濾器是一種高效的自動(dòng)化方式,但在某些情況下,需要更精細(xì)地控制審計(jì)日志的記錄。這時(shí)候只能修改接口代碼,在業(yè)務(wù)邏輯里加入審計(jì)日志記錄。

          這種方式雖然需要直接修改業(yè)務(wù)代碼,但它提供了最大的靈活性和控制能力。

          這個(gè)代碼就沒什么特別的了,直接在接口中調(diào)用 IAuditLogServiceLogAsync 方法來記錄審計(jì)日志即可。

          通過 HttpContext 共享數(shù)據(jù)

          有些參數(shù)是很難在 ActionFilter 里自動(dòng)獲取到的,這些往往跟業(yè)務(wù)邏輯是有關(guān)的,這時(shí)候 HttpContext 就成為了一個(gè)理想的橋梁。

          我們可以將一些臨時(shí)數(shù)據(jù),比如操作前的數(shù)據(jù)快照,存儲(chǔ)在 HttpContext.Items  中,然后在過濾器中訪問這些數(shù)據(jù)來完成審計(jì)日志的記錄。這種方法不僅保持了代碼的解耦,還允許我們靈活地在應(yīng)用的不同部分共享數(shù)據(jù)。

          HttpContext.Items是一個(gè)鍵值對(duì)集合,可用于在一個(gè)請(qǐng)求的生命周期內(nèi)共享數(shù)據(jù)。

          這樣在接口中的代碼就是

                
                HttpContext.Items["AuditLog_OriginalValues"] = item.FeedbackId;
          HttpContext.Items["AuditLog_CurrentValues"] = dto.FeedbackId;
          HttpContext.Items["AuditLog_Changes"] = $"更新反饋結(jié)果 {item.FeedbackId} -> {dto.FeedbackId}";

          注意事項(xiàng)

          • 確保業(yè)務(wù)邏輯和AuditLogAttribute中使用的鍵(如 AuditLog_OriginalValues )唯一且一致,以避免潛在的沖突。這里最好是自己封裝一個(gè) class 來提供這些 const ;
          • 如果業(yè)務(wù)邏輯抽象到了 service 層,則需要注入 IHttpContextAccessor 才能訪問 HttpContext ,這個(gè)服務(wù)可以通過 services.AddHttpContextAccessor() 來注冊(cè);

          日志持久化

          審計(jì)日志的有效持久化是確保長(zhǎng)期安全和合規(guī)性的關(guān)鍵。

          選擇存儲(chǔ)方案

          在選擇最合適的存儲(chǔ)方案時(shí),需要考慮數(shù)據(jù)的重要性、查詢的頻率、成本以及維護(hù)的復(fù)雜性等多個(gè)因素。

          關(guān)系型數(shù)據(jù)庫(RDS)

          關(guān)系型數(shù)據(jù)庫,如MySQL、PostgreSQL等,以其穩(wěn)定性和成熟性受到廣泛認(rèn)可。它們提供了嚴(yán)格的數(shù)據(jù)完整性保障和復(fù)雜查詢的強(qiáng)大能力,適合需要執(zhí)行復(fù)雜分析和報(bào)告的審計(jì)日志。

          • 優(yōu)點(diǎn):數(shù)據(jù)結(jié)構(gòu)化、支持復(fù)雜查詢、成熟的管理工具。
          • 缺點(diǎn):相對(duì)較高的成本、可能需要復(fù)雜的架構(gòu)來支持大規(guī)模數(shù)據(jù)。

          NoSQL數(shù)據(jù)庫

          NoSQL數(shù)據(jù)庫,如MongoDB、Cassandra等,提供了靈活的數(shù)據(jù)模型和良好的橫向擴(kuò)展能力,適合于結(jié)構(gòu)多變或數(shù)據(jù)量巨大的審計(jì)日志。

          • 優(yōu)點(diǎn):高可擴(kuò)展性、靈活的數(shù)據(jù)模型、快速的寫入速度。
          • 缺點(diǎn):查詢功能相對(duì)有限、數(shù)據(jù)一致性模型較弱。

          文件系統(tǒng)

          直接將審計(jì)日志寫入文件系統(tǒng)是最直接的存儲(chǔ)方式,適用于日志量不是特別大或?qū)Σ樵冃枨蟛桓叩膱?chǎng)景。

          • 優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單、成本低廉、易于遷移;
          • 缺點(diǎn):查詢和分析不便、難以管理大量日志文件、擴(kuò)展性有限。

          每種存儲(chǔ)方案都有其適用場(chǎng)景,因此選擇哪一種方案應(yīng)根據(jù)具體需求和資源情況綜合考慮。對(duì)于需要快速寫入和高度可擴(kuò)展的審計(jì)日志系統(tǒng),NoSQL數(shù)據(jù)庫是一個(gè)不錯(cuò)的選擇。

          因此本文選擇了 MongoDB 來記錄日志。

          選擇MongoDB作為審計(jì)日志的存儲(chǔ)方案,不僅因?yàn)樗母咝阅芎涂蓴U(kuò)展性,還因?yàn)樗С朱`活的文檔數(shù)據(jù)模型,使得存儲(chǔ)非結(jié)構(gòu)化或半結(jié)構(gòu)化的審計(jì)數(shù)據(jù)變得簡(jiǎn)單。

          實(shí)現(xiàn) AuditLogMongoService

          在 C# 中使用 MongoDB 非常簡(jiǎn)單。

          需要先添加 MongoDB.Driver 的 nuget 包

                
                dotnet add MongoDB.Driver

          直接上代碼吧,

                
                public class AuditLogMongoService : IAuditLogService {
            private readonly IMongoCollection<AuditLog> _auditLogs;

            public AuditLogMongoService(string connectionString, string databaseName) {
              var client = new MongoClient(connectionString);
              var database = client.GetDatabase(databaseName);
              _auditLogs = database.GetCollection<AuditLog>("audit_logs");
            }

            public async Task LogAsync(AuditLog auditLog) {
              await _auditLogs.InsertOneAsync(auditLog);
            }
          }

          準(zhǔn)備連接字符串&注冊(cè)服務(wù)

          為了避免硬編碼,將連接字符串放在配置文件(appsettings.json)里

                
                "ConnectionStrings": {
            "Redis""redis:6379",
            "MongoDB""mongodb://username:password@path-to-mongo:27017"
          }

          注冊(cè)服務(wù)

                
                builder.Services.AddSingleton<IAuditLogService>(sp => new AuditLogMongoService(builder.Configuration.GetConnectionString("MongoDB"), "db_name"));

          搞定~

          部署 MongoDB

          附上 MongoDB 的部署方法吧,我這里使用 docker ,很方便

                
                version: '3.1'

          services:

            mongo:
              image: mongo:4.4.6
              restart: always
              volumes:
                - ./data:/data/db
              environment:
                MONGO_INITDB_ROOT_USERNAME: username
                MONGO_INITDB_ROOT_PASSWORD: password
              ports:
                - 27017:27017

            mongo-express:
              image: mongo-express
              restart: always
              environment:
                ME_CONFIG_MONGODB_ADMINUSERNAME: username
                ME_CONFIG_MONGODB_ADMINPASSWORD: password
                ME_CONFIG_MONGODB_URL: mongodb://username:password@mongo:27017/
              ports:
                - 8081:8081

          使用 docker-compose 來編排,映射了 27017 和 8081 端口

          可以使用 8081 端口訪問 mongo-express 網(wǎng)頁服務(wù)

          如何查看日志

          • 使用 MongoDB Compass 這個(gè)軟件來查看數(shù)據(jù)
          • 使用 mongo-express 服務(wù)可以在網(wǎng)頁上查看數(shù)據(jù)

          小結(jié)

          雖然是比較簡(jiǎn)單的功能,不過使用 AOP 來實(shí)現(xiàn)用起來感覺還是蠻爽的,不得不說 AspNetCore 的功能確實(shí)豐富


          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩无码A级 | 成人一区二区三区四区五区91电影 | 欧美自拍视频在线观看? | 色操网站| 日韩欧美肏屄高清视频 |