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

相关推荐
程序猿乐锅1 天前
【黑马点评 | 第九篇】秒杀优化-异步实现
java·spring boot·redis·后端·spring·中间件·mybatis
liangshanbo12158 天前
Redux 的中间件(Middleware)的工作机制
中间件·react·redux
今年下半年8 天前
SpringBoot整合金蝶中间件
spring boot·中间件·maven
青山木8 天前
RocketMQ 入门到原理(六):可靠性全景
java·后端·中间件·架构·rocketmq
青山木8 天前
RocketMQ 入门到原理(五):特殊消息类型
java·后端·中间件·架构·rocketmq
青山木9 天前
RocketMQ 入门到原理(三):消息存储原理
java·后端·中间件·架构·rocketmq
csdn_aspnet9 天前
.NetCore Webapi 接口验证请求来源
.netcore·ip·token·签名·webapi·cors·sign
青山木10 天前
RocketMQ 入门到原理(一):整体架构与消息的生命周期
java·分布式·后端·中间件·架构·rocketmq
StevenSurpass11 天前
打印业务彻底解耦:FastPrintAgent 统一 JSON 模型 + FastReport 引擎的设计与落地
mqtt·http·中间件·打印·fastreport
avi911111 天前
无厘头 -- 基于vue 公用状态机,已用于放置类篮球卡牌Game
中间件·vue·公用状态·文件间通信