65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ActionFilters 在 ASP.NET MVC 中创建高级审计跟踪

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2013年4月23日

CPOL

6分钟阅读

viewsIcon

32856

如何在 ASP.NET 中使用 ActionFilters 创建高级审计跟踪

引言

正如我在上一篇关于“使用 ASP.NET MVC ActionFilters 实现简单审计跟踪”的文章中提到的,在处理任何涉及安全和保密性的系统时,问责制是如此重要的一个因素。在那篇文章中,我们讨论了创建一个非常基本的 [Audit] 属性,该属性可用于修饰您的某些控制器和方法,将一些基本的请求信息存储在数据库中,以作为非常简单的审计跟踪。

在这篇后续文章中,我们将实现一些附加功能来充实原始实现,并着眼于存储一些更相关的信息,通过将请求字符串的一些值序列化为 JSON 字符串,然后存储在我们的数据库中,监控用户“Session”以允许我们跟踪单个用户在其 Session 生命周期内的可审计操作,以及其他一些有趣的功能。

问题

上一篇文章中提到的基本审计功能仅记录了当前用户、他们的 IP 地址以及他们正在访问的控制器操作,但这不足够。我们需要能够指定我们希望在审计日志中存储的附加数据有多细粒度,以便审计消息更有意义。

解决方案

为了实现这一点,我们将对现有的审计 ActionFilter 进行几项修改,并添加以下主要功能:

  • Session 捕获 - 我们将实现一个功能,该功能将在特定用户 Session 的所有审计消息之间建立关联(从登录到注销或 Session 过期)。
  • 细粒度数据存储 - 我们将添加一个附加功能,该功能将允许我们指定要存储的值的 object 有多细粒度。这将从第一篇文章中提到的非常简单的审计到序列化整个 Request 对象的内容。
  • JSON 请求序列化 - 我们将使用 JSON 格式来序列化 Request 对象,并且它将与我们进行序列化 Request 对象的深度相关。

因此,我们将需要在我们之前的 Audit 类中添加一些字段,以便它能够处理一些扩展功能。

public class Audit
{
      //A new SessionId that will be used to link an 
      //entire users "Session" of Audit Logs
      //together to help identifier patterns involving erratic behavior
      public string SessionID { get; set; }
      public Guid AuditID { get; set; }
      public string IPAddress { get; set; }
      public string UserName { get; set; }
      public string URLAccessed { get; set; }
      public DateTime TimeAccessed { get; set; }
      //A new Data property that is going to store JSON string 
      //objects that will later be able to be 
      //deserialized into objects if necessary to view details about a Request
      public string Data { get; set; }

      public Audit(){}
}

这两个新属性将足以处理我们计划添加的所有扩展功能。

Session 捕获

Session 捕获将非常基础,我们将使用一些先前在关于 ActionFilters 的另一篇文章中使用过的逻辑,作为识别特定用户的一种非常粗略的方法,基于 IP 地址和 User Agent 等因素来生成一个字符串来生成 MD5 哈希,作为我们的 Session 标识符。

然而,由于我们在“安全”区域内专注于审计,我们将假设当前用户已登录并已通过身份验证。这将允许我们使用他们的 FormsAuthentication cookie 来播种我们的 MD5 哈希,并在其“Session”的整个生命周期中作为他们的 Session 标识符。

var sessionIdentifier = string.Join("", MD5.Create().ComputeHash
(Encoding.ASCII.GetBytes(request.Cookies[FormsAuthentication.FormsCookieName].Value)).Select
(s => s.ToString("x2")));

使用 MD5 哈希来实现这一点并非完全必要,因为身份验证 Cookie 名称可能已经足够独特,但我提到这一点是因为您可能希望使用其他值来生成哈希本身。

细粒度数据存储

我认为,对于特定操作在审计方面的“权重”应该比其他操作更重。因此,您可能希望存储有关处理 POST 事件中非常敏感且易出错的数据的操作的更多信息,而不是仅仅查看记录。

这就是为什么我们将添加一个灵活的系统来确定 Request 对象中的哪些信息应该被序列化,使用一个非常简单的数字系统。

  • 审计级别 0(无序列化)- 不存储实际的 JSON 请求数据。(对象上的 IPAddress 和其他相关属性仍然可用,但不会有任何额外信息
  • 审计级别 1(轻度序列化)- 存储更多 Request 信息,例如 Request 中的 Cookies、Headers 和 Files。
  • 审计级别 2(自定义序列化)- 这会捕获所有上述内容,并添加 Form 对象、QueryString 集合以及 Request 中传递的所有参数。您可以根据您处理的内容自由定制此级别或任何其他级别。
  • 审计级别 3(完整请求序列化)- (序列化所有可序列化字段将在以后的文章中介绍,或者留给用户练习

我们将用于添加这些功能的实际代码将是我们的 AuditAttribute 类中的一个属性。

public int AuditingLevel { get; set; }

以及一个将在属性内部用于应用适当序列化的方法。

//This will serialize the Request object based on the level that you determine
private string SerializeRequest(HttpRequestBase request)
{
       switch (AuditingLevel)
       {
             //No Request Data will be serialized
             case 0: 
             default:
                   return "";
             //Basic Request Serialization - just stores Data
             case 1:
                   return Json.Encode(new { request.Cookies, request.Headers, request.Files});
             //Middle Level - Customize to your Preferences
             case 2:
                   return Json.Encode(new { request.Cookies, request.Headers, 
                   request.Files, request.Form, request.QueryString, request.Params});
             //Highest Level - Serialize the entire Request object 
             //(As mentioned earlier, this will blow up)
             case 3:
                   //We can't simply just Encode the entire request 
                   //string due to circular references as well
                   //as objects that cannot "simply" be 
                   //serialized such as Streams, References etc.
                   return Json.Encode(request);
      }
}

您可以看到序列化部分非常简单,可以通过向要序列化的匿名对象添加其他属性来轻松修改以处理其他自定义,所以尽情发挥吧。

装饰您的方法(适度

由于 Auditing 属性有一个可公开访问的属性来指定我们的序列化和审计级别,因此它将允许您轻松、干净地装饰控制器操作和整个控制器。

这项附加功能不仅需要您编写很少的代码,而且增加了巨大的灵活性,因此您可以为需要密切监控的重要操作使用更高的审计级别,如下所示。

[Audit]
public ActionResult Unimportant() { ... }

[Audit(AuditingLevel = 1)]
public ActionResult SlightlyImportant() { ... }

[Audit(AuditingLevel = 2)]
public ActionResult Important() { ... }

[Audit(AuditingLevel = 3]
public ActionResult Classified() { ... }

这就是确定哪些操作将被“审计”以及将应用何种级别的基本内容。

Advanced Audit Log

一个基本的高级审计日志示例,其中详细介绍了 Audit 类的主要信息。

对于这个非常基本的示例,只需将鼠标悬停在 Data 对象上,您就可以看到其中隐藏的一些值。(这仅用于示例目的,您可以随意按照您认为合适的方式格式化此区域。

Hovering over an Advanced Audit Log Entry

将鼠标悬停在审计日志的 Data 区域将显示有关 Request 对象的其他详细信息。

将此视为起点

本文和上一篇文章中提到的所有示例仅仅是您可以在审计场景中使用 Action Filters 的一些可能性的想法。目前它远非一个完整的系统,但对于那些希望为其代码库创建功能齐全的审计系统或安全框架的人来说,它应该是一个很好的起点。

您可以下载整个示例来随意修改,我希望它能为提高您当前和未来应用程序的问责制和安全性提供基础。

© . All rights reserved.