在 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 中实现了审计跟踪。此实现有助于跟踪数据的变化,这对于维护安全性、合规性和调试问题至关重要。

相关推荐
陌殇殇20 小时前
004 Kafka异常处理
中间件·kafka·springboot
Albert Tan1 天前
Oracle Fusion Middleware 12C安装 - 呆瓜式
oracle·中间件·数据库架构
m0_748254883 天前
docker离线安装及部署各类中间件(x86系统架构)
docker·中间件·系统架构
YGGP4 天前
【Gee】Day5:中间件
中间件·golang
jingwang-cs5 天前
内外网隔离文件传输解决方案|系统与钉钉集成+等保合规,安全提升70%
安全·中间件·钉钉·安全架构
WeiLai11125 天前
面试基础--微服务架构:如何拆分微服务、数据一致性、服务调用
java·分布式·后端·微服务·中间件·面试·架构
web135085886355 天前
全面指南:使用JMeter进行性能压测与性能优化(中间件压测、数据库压测、分布式集群压测、调优)
jmeter·中间件·性能优化
带娃的IT创业者6 天前
《Python实战进阶》专栏 No2: Flask 中间件与请求钩子的应用
python·中间件·flask
HeartRaindj6 天前
【中间件开发】kafka使用场景与设计原理
分布式·中间件·kafka
令狐掌门6 天前
C++中间件DDS介绍
c++·中间件·c++ dds