ASP.NET Core WebApi를 감사하는 방법

18501 단어 dotnetcsharpaspnet

If you have an Api that modifies the core data of a system, you need to log every call to that. In addition, If your system accepts input from a 3rd party system, Or sends an output to a 3rd party system, you also need proper logging in case of a dispute happens in future. In this post, I'm gonna tell you how you can have proper audit log without re-inventing the wheel!



핵심 API 중 하나에 대한 감사 요구 사항에 대한 작업을 선택했습니다. 시작으로 저는 작은 위키 문서를 만들고 "감사"라는 단어에서 우리 모두가 의미하고 기대하는 것을 정의하기 위한 회의를 소집했습니다.

먼저 저장되는 데이터를 정의해야 합니다. 역할 때문에 사람마다 감사 로그에서 다른 세부 정보를 기대할 수 있습니다. 그들은 다른 관심사를 가지고 있거나 삶을 더 쉽게 만들기 위해 추가 정보가 필요할 수 있습니다. 또한 특정 정보(PII 또는 개인 식별 정보라고도 함)에는 특정 규정이 있습니다. 또한 요청/응답 헤더와 요청/응답 본문을 기록할 것인지 여부도 논의했습니다.

둘째, 로그의 저장에 대해 논의해야 합니다. 로그가 저장되는 위치, 수용할 수 있는 성능 저하, 수용할 수 있는 비용 및 이와 같은 질문.

셋째, 로그의 보존 및 쿼리에 대해 논의해야 합니다. 보관 기간, 향후 이러한 데이터를 쿼리하는 방법, 로그를 작성해야 하는 형식, 다른 시스템에 통합할 수 있어야 하는지, 인간 상호 작용 인터페이스가 필요한지, 이와 같은 질문 .

다음으로 사용 가능한 다양한 옵션을 조사하기 시작했습니다. 다양한 라이브러리를 살펴보고 비교한 후 다음과 같은 이유로 최종적으로 Audit.Net WebApi을 선택했습니다.
  • 사용을 시작하는 것은 쉽고 시간 효율적입니다. 컨트롤러/액션 속성, 글로벌 액션 필터, 미들웨어 또는 이들의 조합에 의해 활성화될 수 있습니다. 이렇게 하면 다양한 수준에서 활성화/비활성화해야 하는 경우에 대비하여 현재와 예측 가능한 미래에 충분한 유연성을 제공할 수 있습니다.
  • 다중 스토리지 기능. storage providers의 방대한 목록을 보고 놀랐습니다. 로컬, 클라우드, 데이터베이스에 로그를 저장하거나 사용자 지정 저장소 공급자를 만들 수도 있습니다.
  • 구조화된 출력. 출력은 기본적으로 JSON 형식이므로 나중에 속성을 기반으로 쉽게 쿼리할 수 있습니다. 문자열 검색이 거의 필요하지 않습니다.
  • 사용자 정의 필드를 로그에 추가하거나 쉽게 제거할 수 있습니다. 또한 사용자 정의 필드를 추가/제거하는 데 필요한 코드 스타일과 이것이 우리 팀의 일반적인 코드 작성 방식과 일치하는지 살펴보았습니다.

  • 면책조항: 저는 어떤 식으로든 Audit.Net project과 제휴하지 않습니다.

    감사 로그는 어떻게 생겼습니까?



    출력 샘플은 다음과 같습니다.

    {
        "EventType": "POST User.GetUser",
        "Environment": {
            "UserName": "PC1",
            "MachineName": "192-168-1-1",
            "DomainName": "192-168-1-1",
            "CallingMethodName": "ApiProject.Controllers.UserController.GetUser()",
            "AssemblyName": "ApiProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
            "Culture": ""
        },
        "StartDate": "2021-01-22T02:29:39.130551Z",
        "EndDate": "2021-01-22T02:29:57.809649Z",
        "Duration": 79,
        "Action": {
            "TraceId": "00000001:00000002",
            "HttpMethod": "POST",
            "ControllerName": "User",
            "ActionName": "GetUser",
            "ActionParameters": {
                "userId": 1
            },
            "RequestUrl": "<https://localhost:5006/user/1>",
            "IpAddress": "::1",
            "ResponseStatus": "OK",
            "ResponseStatusCode": 200,
            "RequestBody": {},
            "Headers": {
                "Connection": "keep-alive",
                "Content-Type": "application/json-patch+json",
                "Accept": "application/json",
                "Accept-Encoding": "gzip, deflate, br",
                "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
                "Cookie": "",
                "Host": "localhost:5006",
                "Referer": "<https://localhost:5006/swagger/index.html>",
                "User-Agent": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
                "Origin": "[https://localhost:5006](https://localhost:5006/)",
                "Content-Length": "44",
                "Sec-Fetch-Site": "same-origin",
                "Sec-Fetch-Mode": "cors",
                "Sec-Fetch-Dest": "empty"
            },
            "ResponseHeaders": {}
        }
    }
    


    Audit.Net 사용 방법



    Audit.WebApi.Core 추가




    dotnet add package Audit.WebApi.Core
    


    단일 책임을 더 잘 수행하기 위해 감사를 활성화하고 구성하는 데 필요한 논리를 포함하는 정적 클래스( AuditConfiguration.cs )를 만들었습니다. 또한 프로젝트의 모든 컨트롤러에 대해 활성화하기로 결정했기 때문에 전역 작업 필터 옵션을 선택했습니다.

    public static class AuditConfiguration
    {
    
        // Enables audit log with a global Action Filter
        public static void AddAudit(MvcOptions mvcOptions)
        {   
            mvcOptions.AddAuditFilter(config => config
            .LogAllActions()
            .WithEventType("{verb} {controller}.{action}")
            .IncludeHeaders()
            .IncludeRequestBody()
            .IncludeResponseHeaders()
            );
        }
    
        // Configures what and how is logged or is not logged
        public static void ConfigureAudit(IServiceCollection serviceCollection)
        {
           // This is explained below
        }
    }
    


    로그 출력 구성



    필요한 모든 구성을 정의하는 데 도움이 되는 전역 정적 Audit.Core.Configuration 객체가 있습니다.

    FileLog에서 클라우드 Blob 스토리지, 클라우드 데이터베이스 및 Apache Kafka에 이르기까지 many storage providers이 있습니다. 로그를 콘솔에 간단하게 작성하고 싶었습니다. 그래서 로그가 출력될 때 수행해야 하는 작업을 람다 식으로 정의할 수 있는 DynamicAsyncDataProvider를 사용하기로 결정했습니다.

    // Configure audit output
    Audit.Core.Configuration.Setup()
    .UseDynamicAsyncProvider(config => config
    .OnInsert(``async` `ev => Console.WriteLine(ev.ToJson())));
    


    감사 속성 추가/제거



    모든 로그는 AuditScope에서 캡처됩니다. AuditScope에는 이벤트 및 작업 개체에 대한 몇 가지 일반 정보가 포함되어 있습니다. 작업 개체를 가져오려면 GetWebApiAuditAction 확장 메서드를 사용해야 합니다.

    Audit.Core.Configuration.AddCustomAction(ActionType.OnEventSaving, scope =>
    {
    
        var auditAction = scope.Event.GetWebApiAuditAction();
        if (auditAction == null)
        {
            return;
        }
    
        // Removing sensitive headers
        auditAction.Headers.Remove("Authorization");
    
        // Adding custom details to the log
        scope.Event.CustomFields.Add("User", new { Name = "UserName", Id = "1234" });
    
        // Removing request body conditionally as an example
        if (auditAction.HttpMethod.Equals("DELETE"))
        {
            auditAction.RequestBody = null;
        }
    
    });
    


    Scope.Event 객체는 내부적으로 Newtonsoft.Json 라이브러리의 도움으로 JSON으로 직렬화됩니다.

    서비스에 추가



    이제 모든 것을 정의했으므로 Startup.cs 클래스에서 이 두 메서드를 사용하면 됩니다. ConfigureServices 메서드에서 다음과 같이 사용합니다.

    services.AddControllers(configure =>
    {
       AuditConfiguration.ConfigureAudit(services);
       AuditConfiguration.AddAudit(configure);
    }
    


    그게 다야!

    API를 실행하고 http 호출을 수행하고 표준 출력에서 ​​전체 감사 로그를 확인합니다. 짜잔!

    좋은 웹페이지 즐겨찾기