在 ASP.NET Core Web API 中实现审计跟踪

一.介绍

审计跟踪对于跟踪数据变化、维护安全性规至关重要。在本文中,我们将在 ASP.NET Core Web API 中实现审计跟踪。该示例将涵盖从设置项目到执行 CRUD 操作和验证审计日志的所有内容。

二.先决条件

  1. Visual Studio 或 Visual Studio Code
  2. SQL Server(或合适的 SQL 数据库)

三.实现方法

步骤 1.创建一个新项目

打开终端或命令提示符并运行以下命令来创建一个新的 ASP.NET Core Web API 项目:

复制代码
dotnet new webapi -n AuditTrailExample
cd AuditTrailImplementtionInAspNetCoreWebAPI

步骤 2.安装所需的 NuGet 包

安装 Entity Framework Core 和 SQL Server 必要的包。

复制代码
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.AspNetCore.Http.Abstractions

步骤 3. 定义数据模型

创建产品和审计日志实体。

3.1.产品实体

创建一个新的文件夹 Models 并添加 Product 类。

复制代码
namespace AuditTrailImplementtionInAspNetCoreWebAPI.Model
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }

}
3.2.审计日志实体

添加 AuditLog 类。

复制代码
namespace AuditTrailImplementtionInAspNetCoreWebAPI.Model
{
    public class AuditLog
    {
        public int Id { get; set; }
        public string? UserId { get; set; }
        public DateTime Timestamp { get; set; }
        public string? Action { get; set; }
        public string? TableName { get; set; }
        public string? RecordId { get; set; }
        public string? Changes { get; set; }
    }
}

步骤 4.配置数据库上下文

创建新文件夹Data并添加ApplicationDbContext类:

复制代码
using AuditTrailImplementtionInAspNetCoreWebAPI.Model;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using System.Collections.Generic;
namespace AuditTrailImplementtionInAspNetCoreWebAPI.Data
{
    public class ApplicationDbContext : DbContext
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor httpContextAccessor)
            : base(options)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        public DbSet<AuditLog> AuditLogs { get; set; }
        public DbSet<Product> Products { get; set; }
        public override int SaveChanges()
        {
            var auditEntries = OnBeforeSaveChanges();
            var result = base.SaveChanges();
            OnAfterSaveChanges(auditEntries);
            return result;
        }
        private List<AuditEntry> OnBeforeSaveChanges()
        {
            ChangeTracker.DetectChanges();
            var auditEntries = new List<AuditEntry>();
            var userId = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
            foreach (var entry in ChangeTracker.Entries())
            {
                if (entry.Entity is AuditLog || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
                {
                    continue;
                }
                var auditEntry = new AuditEntry(entry)
                {
                    TableName = entry.Entity.GetType().Name,
                    Action = entry.State.ToString(),
                    UserId = "1234"
                };
                auditEntries.Add(auditEntry);
                foreach (var property in entry.Properties)
                {
                    string propertyName = property.Metadata.Name;
                    if (property.IsTemporary)
                    {
                        auditEntry.TemporaryProperties.Add(property);
                        continue;
                    }
                    if (entry.State == EntityState.Added)
                    {
                        auditEntry.NewValues[propertyName] = property.CurrentValue;
                    }
                    else if (entry.State == EntityState.Deleted)
                    {
                        auditEntry.OldValues[propertyName] = property.OriginalValue;
                    }
                    else if (entry.State == EntityState.Modified && property.IsModified)
                    {
                        auditEntry.OldValues[propertyName] = property.OriginalValue;
                        auditEntry.NewValues[propertyName] = property.CurrentValue;
                    }
                }
            }
            foreach (var auditEntry in auditEntries.Where(e => !e.HasTemporaryProperties))
            {
                AuditLogs.Add(auditEntry.ToAuditLog());
            }
            return auditEntries.Where(e => e.HasTemporaryProperties).ToList();
        }
        private void OnAfterSaveChanges(List<AuditEntry> auditEntries)
        {
            if (auditEntries == null || auditEntries.Count == 0)
            {
                return;
            }
            foreach (var auditEntry in auditEntries)
            {
                foreach (var prop in auditEntry.TemporaryProperties)
                {
                    if (prop.Metadata.IsPrimaryKey())
                    {
                        auditEntry.KeyValues[prop.Metadata.Name] = prop.CurrentValue;
                    }
                    else
                    {
                        auditEntry.NewValues[prop.Metadata.Name] = prop.CurrentValue;
                    }
                }
                AuditLogs.Add(auditEntry.ToAuditLog());
            }
            SaveChanges();
        }
    }
}
4.1.添加连接字符串

在 appsettings.json 中,将连接字符串添加到您的 SQL Server:

复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=SARDAR-MUDASSAR\\SQLEXPRESS2022;database=AuditLog;User ID=sa;Password=Smak$95;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"
  },
  "AllowedHosts": "*"
}

步骤 5.配置服务和中间件

更新 Program.cs 文件以配置服务和中间件:

复制代码
using AuditTrailImplementtionInAspNetCoreWebAPI.Data;
using Microsoft.EntityFrameworkCore;
namespace AuditTrailImplementtionInAspNetCoreWebAPI
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // Add services to the container.
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
            builder.Services.AddControllers();
            builder.Services.AddHttpContextAccessor();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            var app = builder.Build();
            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }
            app.UseHttpsRedirection();
            app.UseAuthorization();
            app.MapControllers();
            app.Run();
        }
    }
}

步骤 6. 创建产品控制器

创建一个 Controllers 文件夹并添加 ProductsController 类。

复制代码
using AuditTrailImplementtionInAspNetCoreWebAPI.Data;
using AuditTrailImplementtionInAspNetCoreWebAPI.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace AuditTrailImplementtionInAspNetCoreWebAPI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ProductsController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        public ProductsController(ApplicationDbContext context)
        {
            _context = context;
        }
        [HttpGet]
        public async Task<IActionResult> GetProducts()
        {
            var products = await _context.Products.ToListAsync();
            return Ok(products);
        }
        [HttpGet("{id}")]
        public async Task<IActionResult> GetProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }
        [HttpPost]
        public async Task<IActionResult> CreateProduct([FromBody] Product newProduct)
        {
            _context.Products.Add(newProduct);
            await _context.SaveChangesAsync();
            return CreatedAtAction(nameof(GetProduct), new { id = newProduct.Id }, newProduct);
        }
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateProduct(int id, [FromBody] Product updatedProduct)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null)
            {
                return NotFound();
            }
            product.Name = updatedProduct.Name;
            product.Price = updatedProduct.Price;
            product.Stock = updatedProduct.Stock;
            await _context.SaveChangesAsync();
            return NoContent();
        }
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null)
            {
                return NotFound();
            }
            _context.Products.Remove(product);
            await _context.SaveChangesAsync();
            return NoContent();
        }
    }
}

步骤 7. 应用迁移并更新数据库

生成迁移文件并更新数据库。

复制代码
add migration IMAuditTrail22042024
Update-database

步骤 8. 测试实施

使用 API 执行 CRUD 操作并验证 AuditLogs 表是否填充了适当的条目。

四.CRUD 操作示例

  1. 创建新产品

    curl -X POST "https://localhost:5001/products" -H "accept: text/plain" -H "Content-Type: application/json" -d "{ "name": "New Product", "price": 19.99, "stock": 100 }"

  2. 更新产品

    curl -X PUT "https://localhost:5001/products/1" -H "accept: text/plain" -H "Content-Type: application/json" -d "{ "name": "Updated Product", "price": 29.99, "stock": 150 }"

  3. 删除产品

    curl -X DELETE "https://localhost:5001/products/1" -H "accept: text/plain"

五.检查审计日志

执行上述操作后,检查数据库中的 AuditLogs 表,确保其中填充了预期的条目。

六.结论

通过遵循此端到端示例,您已成功在 ASP.NET Core Web API 中实现了审计跟踪。此实现有助于跟踪数据的变化,这对于维护安全性、合规性和调试问题至关重要。

相关推荐
桔子雨15 小时前
轻量,再轻量:当 PicoServer 遇上 SimdPaddleOCR
webapi·picoserver·轻量web框架
2401_8685347818 小时前
中间件概念
中间件
小刘在重生~18 小时前
Django 全套入门笔记|安装、项目创建、ORM、Form、中间件、登录认证
笔记·中间件·django
花间相见2 天前
【LangChain中间件01】—— LangChain 中间件入门:六个钩子让 Agent 可插拔
中间件·langchain
姚不倒3 天前
HAProxy 系列(四):多中间件统一 VIP 入口 — 架构模式与最佳实践
运维·中间件·架构·haproxy
FungLeo3 天前
成为全栈·Node 后端篇·错误处理:异常分层与全局捕获
node.js·错误处理·异常分层·全局捕获·成为全栈
yoguo-2103 天前
TongWeb7控制台部署应用时,应用包大小限制
中间件·tongweb
__zRainy__4 天前
Node系列 · Express:cors 中间件
中间件·express
StevenSurpass4 天前
智能工厂场景:FastPrintAgent 分布式打印中间件落地应用方案
分布式·mqtt·http·中间件·打印·fastreport
程序员小八7776 天前
Go Web 工程化:日志、配置与错误处理中间件,让服务「能上线」
前端·中间件·golang