项目

审计日志

维基百科:"审计追踪(又称审计日志)是按时间顺序记录的安全相关活动序列,提供操作、流程或事件受影响过程的书面证据"。

ABP提供了一套可扩展的审计日志系统,通过约定实现自动化日志记录,并提供配置点来控制审计日志的详细程度。

每个Web请求通常会创建并保存一个审计日志对象(详见下文审计日志对象章节),包含:

  • 请求与响应详情(如URL、HTTP方法、浏览器信息、HTTP状态码等)
  • 执行的操作(控制器动作及应用服务方法调用及其参数)
  • Web请求中发生的实体变更
  • 异常信息(请求执行过程中出现错误时)
  • 请求耗时(用于衡量应用性能)

启动模板已预配置适用于大多数应用的审计日志系统。本文档将帮助您实现更精细的审计日志控制。

数据库提供程序支持

UseAuditing()

需在ASP.NET Core请求管道中添加UseAuditing()中间件以创建和保存审计日志。若使用启动模板创建应用,此配置已自动完成。

AbpAuditingOptions

AbpAuditingOptions是配置审计日志系统的核心选项对象。可在模块的ConfigureServices方法中配置:

Configure<AbpAuditingOptions>(options =>
{
    options.IsEnabled = false; //禁用审计系统
});

可配置选项包括:

  • IsEnabled(默认:true):总开关,启用/禁用审计系统。设为false时其他选项无效
  • HideErrors(默认:true):保存审计日志对象时若发生错误,系统将隐藏错误并写入常规日志。若审计日志对系统至关重要,可设为false以抛出异常
  • IsEnabledForAnonymousUsers(默认:true):若仅需为认证用户记录日志,可设为false。匿名用户的UserId将显示为null
  • AlwaysLogOnException(默认:true):设为true时,发生异常/错误将始终保存审计日志(不受其他选项限制,但受IsEnabled控制)
  • IsEnabledForIntegrationService(默认:false):默认禁用集成服务的审计日志,设为true启用
  • IsEnabledForGetRequests(默认:false):HTTP GET请求通常不引发数据库变更,系统默认不记录。设为true可启用GET请求日志
  • DisableLogActionInfo(默认:false):设为true时将停止记录AuditLogActionInfo
  • ApplicationName:多应用共享审计日志数据库时,可设置此属性以区分不同应用日志。未设置时默认使用IApplicationInfoAccessor.ApplicationName值(即入口程序集名称)
  • IgnoredTypes:审计日志忽略的Type列表。若为实体类型,则该类实体的变更不会被记录。此列表也用于动作参数序列化
  • EntityHistorySelectors:用于确定是否记录实体变更的选择器列表,详见下文
  • SaveEntityHistoryWhenNavigationChanges(默认:true):设为true时,导航属性变更将保存实体变更记录
  • ContributorsAuditLogContributor实现列表,用于扩展审计日志系统,详见"审计日志贡献者"章节
  • AlwaysLogSelectors:匹配条件时始终保存审计日志的选择器列表

实体历史选择器

记录所有实体的全部变更将占用大量数据库空间,因此除非显式配置,审计日志系统默认不保存任何实体变更

若要记录所有实体变更,可使用AddAllEntities()扩展方法:

Configure<AbpAuditingOptions>(options =>
{
    options.EntityHistorySelectors.AddAllEntities();
});

options.EntityHistorySelectors实际上是类型谓词列表,可通过lambda表达式定义筛选条件。

下例选择器实现了与上述AddAllEntities()相同的效果:

Configure<AbpAuditingOptions>(options =>
{
    options.EntityHistorySelectors.Add(
        new NamedTypeSelector(
            "MySelectorName",
            type =>
            {
                if (typeof(IEntity).IsAssignableFrom(type))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        )
    );
});

条件typeof(IEntity).IsAssignableFrom(type)对所有实现IEntity接口的类返回true(即应用中所有实体)。可根据需求条件化返回truefalse

options.EntityHistorySelectors提供了灵活的实体选择机制。另一种方式是在每个实体上使用AuditedDisableAuditing特性。

AbpAspNetCoreAuditingOptions

AbpAspNetCoreAuditingOptions用于配置ASP.NET Core层的审计日志选项。可在模块的ConfigureServices方法中配置:

Configure<AbpAspNetCoreAuditingOptions>(options =>
{
    options.IgnoredUrls.Add("/products");
});

IgnoredUrls是唯一选项,用于设置忽略的URL前缀列表。上例中所有以/products开头的URL将不记录审计日志。

AbpAspNetCoreAuditingUrlOptions

AbpAspNetCoreAuditingUrlOptions用于配置ASP.NET Core层的审计日志选项。可在模块的ConfigureServices方法中配置:

Configure<AbpAspNetCoreAuditingUrlOptions>(options =>
{
    options.IncludeQuery = true;
});

可配置选项包括:

  • IncludeSchema(默认:false):设为true时URL包含架构信息
  • IncludeHost(默认:false):设为true时URL包含主机信息
  • IncludeQuery(默认:false):设为true时URL包含查询字符串

服务级审计日志启用/禁用

控制器与动作的启用/禁用

默认记录所有控制器动作(GET请求受IsEnabledForGetRequests控制)。

可使用[DisableAuditing]禁用特定控制器:

[DisableAuditing]
public class HomeController : AbpController
{
    //...
}

在动作级别使用[DisableAuditing]进行控制:

public class HomeController : AbpController
{
    [DisableAuditing]
    public async Task<ActionResult> Home()
    {
        //...
    }

    public async Task<ActionResult> OtherActionLogged()
    {
        //...
    }
}

应用服务与方法的启用/禁用

默认记录应用服务方法调用。可在服务或方法级别使用[DisableAuditing]

其他服务的启用/禁用

动作审计日志可应用于任何类(通过依赖注入注册并解析),但默认仅启用控制器和应用服务。

需审计日志的类或方法可使用[Audited][DisableAuditing]。此外,类可通过(直接或间接)实现IAuditingEnabled接口默认启用审计日志。

实体与属性的启用/禁用

以下情况忽略实体变更审计日志:

  • 实体类型已加入AbpAuditingOptions.IgnoredTypes列表(如前所述)
  • 对象非实体(未直接或间接实现IEntity接口——所有实体默认实现此接口)
  • 实体类型非公开

其他情况下,可使用Audited启用实体变更审计日志:

[Audited]
public class MyEntity : Entity<Guid>
{
    //...
}

或禁用实体审计日志:

[DisableAuditing]
public class MyEntity : Entity<Guid>
{
    //...
}

仅当实体被AbpAuditingOptions.EntityHistorySelectors选中时才需禁用审计日志。

可通过禁用特定属性实现精细控制:

[Audited]
public class MyUser : Entity<Guid>
{
    public string Name { get; set; }
        
    public string Email { get; set; }

    [DisableAuditing] //审计日志忽略密码字段
    public string Password { get; set; }
}

系统将记录MyUser实体变更,但忽略可能危及安全的Password属性。

有时可能需仅记录少量属性而忽略其他。为避免为每个属性添加[DisableAuditing],可在实体标记[DisableAuditing]后仅为所需属性添加[Audited]

[DisableAuditing]
public class MyUser : Entity<Guid>
{
    [Audited] //仅记录Name变更
    public string Name { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

忽略更新审计属性与发布实体更新事件

[DisableAuditing]特性在应用于实体属性时支持额外配置选项:

  • UpdateModificationProps(默认:true):设为false时,该属性变更不更新审计属性(如LastModificationTime
  • PublishEntityEvent(默认:true):设为false时,该属性变更不发布实体变更事件(EntityUpdatedEvent
public class MyUser : Entity<Guid>
{
    public string Name { get; set; }

    [DisableAuditing(UpdateModificationProps = false, PublishEntityEvent = false)]
    public string ReadCount { get; set; }
}

此例将在 ReadCount 属性更新时忽略更新审计属性并禁止发布实体更新事件。

UpdateModificationPropsPublishEntityEvent 仅适用于 Entity Framework Core ,不适用于 MongoDB

IAuditingStore

IAuditingStore 接口用于保存审计日志对象(详见下文)。若需将审计日志保存至自定义数据存储,可实现此接口并通过 依赖注入系统 替换。

未注册审计存储时使用 SimpleLogAuditingStore,仅将审计对象写入标准 日志系统

启动模板 中预配置的 审计日志模块 将审计日志对象保存至数据库(支持多数据库提供程序)。多数情况下无需关注 IAuditingStore 的实现细节。

审计日志对象

默认每个Web请求创建一个审计日志对象,其结构关系如下图所示:

auditlog-object-diagram

  • AuditLogInfo:根对象,包含以下属性:
    • ApplicationName:多应用共享数据库时区分应用日志
    • UserId:当前用户ID(已登录时)
    • UserName:当前用户名(已登录时,此值用于避免依赖身份模块查询)
    • TenantId:多租户应用中的当前租户ID
    • TenantName:多租户应用中的当前租户名
    • ExecutionTime:审计日志对象创建时间
    • ExecutionDuration:请求总执行时长(毫秒),用于性能监控
    • ClientId:当前客户端ID(已认证时)。客户端通常为通过HTTP API使用系统的第三方应用
    • ClientName:当前客户端名(可用时)
    • ClientIpAddress:客户端/用户设备IP地址
    • CorrelationId:当前关联ID,用于关联不同应用(或微服务)在单一逻辑操作中写入的审计日志
    • BrowserInfo:当前用户浏览器信息(可用时)
    • HttpMethod:当前请求的HTTP方法(GET、POST、PUT、DELETE等)
    • HttpStatusCode:请求的HTTP响应状态码
    • Url:请求URL
  • AuditLogActionInfo:审计日志动作通常是Web请求期间的控制器动作或应用服务方法调用。一个审计日志可包含多个动作,动作对象包含:
    • ServiceName:执行的控制器/服务名
    • MethodName:执行的控制器/服务方法名
    • Parameters:方法参数的JSON格式文本
    • ExecutionTime:方法执行时间
    • ExecutionDuration:方法执行时长(毫秒),用于性能监控
  • EntityChangeInfo:表示Web请求中的实体变更。审计日志可包含零或多个实体变更,变更信息包含:
    • ChangeTime:实体变更时间
    • ChangeType:枚举值:Created (0)、Updated (1)、Deleted (2)
    • EntityId:变更实体的ID
    • EntityTenantId:实体所属租户ID
    • EntityTypeFullName:实体类型全名(如Book实体为Acme.BookStore.Book
  • EntityPropertyChangeInfo:表示实体属性的变更。实体变更信息可包含一或多个属性变更,包含:
    • NewValue:属性新值。实体删除时为null
    • OriginalValue:变更前的旧值/原始值。实体新建时为null
    • PropertyName:实体类的属性名
    • PropertyTypeFullName:属性类型全名
  • Exception:审计日志对象可包含零或多个异常,用于记录失败请求的详细信息
  • Comment:可向审计日志条目添加自定义消息的任意字符串值,审计日志对象可包含零或多个注释

除上述标准属性外,AuditLogInfoAuditLogActionInfoEntityChangeInfo对象实现了IHasExtraProperties接口,可添加自定义属性。

审计日志贡献者

可通过继承AuditLogContributor类扩展审计系统,该类定义了PreContributePostContribute方法。

唯一预构建的贡献者是AspNetCoreAuditLogContributor类,用于设置HTTP请求的相关属性。

贡献者可设置AuditLogInfo类的属性和集合以添加更多信息。

示例:

public class MyAuditLogContributor : AuditLogContributor
{
    public override void PreContribute(AuditLogContributionContext context)
    {
        var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
        context.AuditInfo.SetProperty(
            "MyCustomClaimValue",
            currentUser.FindClaimValue("MyCustomClaim")
        );
    }

    public override void PostContribute(AuditLogContributionContext context)
    {
        context.AuditInfo.Comments.Add("Some comment...");
    }
}
  • context.ServiceProvider可用于从依赖注入解析服务
  • context.AuditInfo可用于操作当前审计日志对象

创建贡献者后,需将其加入AbpAuditingOptions.Contributors列表:

Configure<AbpAuditingOptions>(options =>
{
    options.Contributors.Add(new MyAuditLogContributor());
});

IAuditLogScope & IAuditingManager

本节介绍高级用法中的IAuditLogScopeIAuditingManager服务。

审计日志范围是一个环境范围,用于构建保存审计日志对象(如前所述)。默认情况下,审计日志中间件(见上文UseAuditing()章节)为Web请求创建审计日志范围。

访问当前审计日志范围

前文所述的审计日志贡献者是操作审计日志对象的全局方式,适用于从服务获取值。

若需在应用任意点操作审计日志对象,可访问当前审计日志范围并获取当前审计日志对象(独立于范围管理方式)。示例:

public class MyService : ITransientDependency
{
    private readonly IAuditingManager _auditingManager;

    public MyService(IAuditingManager auditingManager)
    {
        _auditingManager = auditingManager;
    }

    public async Task DoItAsync()
    {
        var currentAuditLogScope = _auditingManager.Current;
        if (currentAuditLogScope != null)
        {
            currentAuditLogScope.Log.Comments.Add(
                "Executed the MyService.DoItAsync method :)"
            );
            
            currentAuditLogScope.Log.SetProperty("MyCustomProperty", 42);
        }
    }
}

需始终检查_auditingManager.Current是否为null,因其由外部范围控制,无法预知调用方法前是否已创建审计日志范围。

手动创建审计日志范围

极少需要手动创建审计日志范围,但必要时可使用IAuditingManager创建:

public class MyService : ITransientDependency
{
    private readonly IAuditingManager _auditingManager;

    public MyService(IAuditingManager auditingManager)
    {
        _auditingManager = auditingManager;
    }

    public async Task DoItAsync()
    {
        using (var auditingScope = _auditingManager.BeginScope())
        {
            try
            {
                //调用其他服务...
            }
            catch (Exception ex)
            {
                //添加异常
                _auditingManager.Current.Log.Exceptions.Add(ex);
                throw;
            }
            finally
            {
                //始终保存日志
                await auditingScope.SaveAsync();
            }
        }
    }
}

可调用其他服务(可能引发进一步调用或实体变更),所有这些交互将在finally块中作为单一审计日志对象保存。

审计日志模块

审计日志模块基本实现了IAuditingStore接口,将审计日志对象保存至数据库,支持多数据库提供程序。此模块默认包含在启动模板中。

详见审计日志模块文档

在本文档中