内容安全策略属性 Mvc - 添加多次

Content Security Policy Attribute Mvc - Adding Many Times

我正在使用 ASP.NET MVC 5。我编写了一个小的过滤器属性,用于将内容安全策略添加到响应 header。这是代码:

public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpResponseBase response = filterContext.HttpContext.Response;

        response.AddHeader("Content-Security-Policy", "default-src *; " +
          "img-src * data:; " +
          "style-src 'self' 'unsafe-inline' http://fonts.googleapis.com https://fonts.googleapis.com; " +
          "script-src 'self' 'unsafe-inline' 'unsafe-eval' " +

          "localhost:*/* " +

          "https://facebook.com " + 
          "*.facebook.com " +

          "https://facebook.net " + 
          "*.facebook.net " +

          "https://onesignal.com " +
          "*.onesignal.com " +

          "https://abtasty.com *.abtasty.com *.convertexperiments.com " + 

          "http://www.googletagmanager.com " +
          "https://www.googletagmanager.com " +

          "http://www.google-analytics.com " +
          "https://www.google-analytics.com " +

          "http://www.googleadservices.com " +
          "https://www.googleadservices.com ");

        base.OnActionExecuting(filterContext);
    }
}

到过滤器配置:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //...
        filters.Add(new ContentSecurityPolicyFilterAttribute());
    }
}

我的问题是此代码在每个请求中都添加了多个 Content-Security-Policy header。它应该 运行 一次,并且只添加一次 header 。我说得对吗?

那么我该如何解决这个问题?

要解决此问题,您需要发现为什么 您的过滤器被多次调用(假设是!),我找到了一种发生方式,但它对你来说可能不一样(将调试器附加到过滤器的第一行并查看调用堆栈以查看是什么触发了它)。

我在一个空的 MVC 项目(具有默认的 HomeController 和 co.)中使用了以下简化的情况来验证在最简单的情况下,ContentSecurityPolicyFilterAttribute 过滤器只得到执行一次:

// Truncated CSP filter
public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpResponseBase response = filterContext.HttpContext.Response;

        response.AddHeader("Content-Security-Policy", "default-src *; img-src * data:; ");

        base.OnActionExecuting(filterContext);
    }
}

// Addition to FilterConfig.cs
filters.Add(new ContentSecurityPolicyFilterAttribute());

使用上面的代码,即使 HomeController 中的 Index 操作更改为:

return RedirectToAction("Contact");

两个请求都显示(使用 Fiddler),对 Index 的请求返回一个 HTTP 302 重定向到 /Home/Contact,并且都只包含一个 CSP header.

如何获得多个Content-Security-Policy header

Partial Views.

如果您使用的是部分视图,那么这很可能是重复 header 的原因,特别是如果它们调用控制器操作来填充它们自己。

通过将以下代码添加到 HomeController:

public ActionResult PartialContentForHome()
{
    return View("PartialContentForHome");
}

在 Views\Shared 下创建一个名为 PartialContentForHome.cshtml 的新分部视图,其中包含以下内容 mark-up(为了直观证明正在调用分部视图):

@{
    Layout = null;
}
<h1>
    Partial!
</h1>

最后,将 @Html.Action("PartialContentForHome", "Home") 添加到视图文件 Views\Home\Index.cshtml,您:

  1. 获取由 Views\Home\Index.cshtmlViews\Shared\PartialContentForHome.cshtml 组成的页面,即文本 "Partial!" 将显示在页面
  2. 将命中已在 CSP 过滤器第一行设置的 break-point 两次
  3. 将在发送给客户端的响应中看到 CSP header 的两个实例

如何避免拥有多个header

如果问题是由部分 views/invoking 控制器操作引起的,您需要确保过滤器将自身调节为每个请求只执行一次。一种方法是将一个值填充到 HttpContext.Items collection 中用作 "I've already added the header" 标记,例如:

public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.RequestContext.HttpContext.Items.Contains(nameof(ContentSecurityPolicyFilterAttribute)))
        {
            HttpResponseBase response = filterContext.HttpContext.Response;

            response.AddHeader("Content-Security-Policy", "default-src *; img-src * data:; ");
            filterContext.RequestContext.HttpContext.Items.Add(nameof(ContentSecurityPolicyFilterAttribute), string.Empty);
        }
        base.OnActionExecuting(filterContext);
    }
}

在此过滤器的更新版本中,无论何时执行它都会在 HttpContext.Items 中查找以其自身命名的条目。如果不存在,它会添加 header,然后向 HttpContext.Items 添加一个条目,这样如果它再次出现 运行,则 Content-Security-Policy header 不会没有得到 re-added。这将在 general 情况下起作用,以确保每个请求不会多次执行过滤器,但我们可以更好地处理 header,特别是:

public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpResponseBase response = filterContext.HttpContext.Response;
        var header = response.Headers["Content-Security-Policy"];
        if (header == null)
        {
            response.AddHeader("Content-Security-Policy", "default-src *; img-src * data:; ");
        }
        base.OnActionExecuting(filterContext);
    }
}

即我们需要做的就是检查 header 是否在 Response 的 Headers collection 中,如果是,则不再添加。