CORS,飞行前选项,IIS和WebApi2

时间:2019-06-06 01:50:50

标签: c# cors asp.net-web-api2

对另一台服务器上的WebApi2端点的SPA提取调用停止工作。这两个人已经好多年了,但是我最近重新发布了两端,所以事情有点模棱两可。

Edge在这方面比Chrome更有用,它告诉我我收到405作为对OPTIONS请求的响应。 SO和整个网络上的混乱声让我想起了在Kestrel托管的SPA中设置CORS的过程,并且在源代码中看到的内容表明我已经做到了。查看请求标头会显示所需标头和期望值。这并不奇怪,因为得到405个对OPTIONS请求的响应意味着SPA正在<发送>发送,这意味着已对其进行了适当配置。

更多的调查发现,我应该将NuGet软件包Microsoft.AspNet.WebApi.Cors添加到WebApi项目中。检查发现它已经存在。

在web.config中查看WebApi可以发现

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

我相信WebApi末端的CORS软件包的重点是处理OPTIONS动词。但是405响应说这没有发生,因此我注释了<remove name="OPTIONSVerbHandler" />和Lo!他们再次成为朋友。

问题解决了吗?并不是的。在应用程序中处理此问题的目的是使它减少对外部定义的配置选项的依赖。这引出了我的问题:

除了用NuGet添加Microsoft.AspNet.WebApi.Cors之外,您还需要做什么?

这是一项WebApi2服务,当CORS不在老朋友之间时,它可以正常工作。它运行在.NET Framework 4.7.2上,并使用最新Service Pack托管在Server 2012的IIS上。客户端和服务器软件都在专用网络上运行,因此CORS引入了复杂性来解决我们没有的问题。知道如何使其关闭并停止帮助非常有用。

1 个答案:

答案 0 :(得分:1)

我在MSDN article by Brock Allen中找到了问题的答案。

与SO有关仅链接的回答的政策保持一致,下面回答了该问题的文章部分,以防止链接中断。我想强调的是,以下内容不是我的工作,而完全归因于思想技术的布罗克·艾伦。


Web API 2中的CORS支持

Web API中的CORS支持是一个完整的框架,用于允许应用程序定义CORS请求的权限。该框架围绕策略的概念,该策略使您可以为应用程序中的任何给定请求指定允许的CORS功能。

首先,为了获得CORS框架,您必须从Web API应用程序中引用CORS库(默认情况下,Visual Studio 2013中的任何Web API模板都未引用它们)。 Web API CORS框架可通过NuGet作为Microsoft.AspNet.WebApi.Cors包获得。如果您不使用NuGet,则它也可以作为Visual Studio 2013的一部分使用,并且您需要引用两个程序集:System.Web.Http.Cors.dll和System.Web.Cors.dll(在我的计算机上,位于C:\ Program Files(x86)\ Microsoft ASP.NET \ ASP.NET Web Stack 5 \ Packages中。

接下来,为了表达该策略,Web API提供了一个名为EnableCorsAttribute的自定义属性类。此类包含以下属性:允许的来源,HTTP方法,请求标头,响应标头以及是否允许使用凭据(该模型模拟了前面讨论的CORS规范的所有详细信息)。

最后,为了使Web API CORS框架处理CORS请求并发出适当的CORS响应标头,它必须查看应用程序中的每个请求。 Web API具有通过消息处理程序进行此类拦截的可扩展点。适当地,Web API CORS框架实现了一个称为CorsMessageHandler的消息处理程序。对于CORS请求,它将为正在调用的方法查询属性中表示的策略,并发出适当的CORS响应标头。

EnableCorsAttribute :EnableCorsAttribute类是应用程序可以表达其CORS策略的方式。 EnableCorsAttribute类具有重载的构造函数,可以接受三个或四个参数。参数(按顺序)是:

  1. 允许的起源列表
  2. 允许的请求标头列表
  3. 允许的HTTP方法列表
  4. 允许的响应标题列表(可选)

还有一个属性用于允许凭据(支持Support凭据),另一个属性用于指定预检缓存持续时间值(PreflightMaxAge)。

图2 显示了将EnableCors属性应用于控制器上各个方法的示例。用于各种CORS策略设置的值应与前面示例中显示的CORS请求和响应相匹配。

图2将EnableCors属性应用于操作方法

public class ResourcesController : ApiController
{
  [EnableCors("http://localhost:55912", // Origin
              null,                     // Request headers
              "GET",                    // HTTP methods
              "bar",                    // Response headers
              SupportsCredentials=true  // Allow credentials
  )]
  public HttpResponseMessage Get(int id)
  {
    var resp = Request.CreateResponse(HttpStatusCode.NoContent);
    resp.Headers.Add("bar", "a bar value");
    return resp;
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "PUT",                          // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "POST",                         // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

请注意,每个构造函数参数都是一个字符串。通过指定以逗号分隔的列表(如在图2 中为允许的请求标头指定的列表)来指示多个值。如果希望允许所有来源,请求标头或HTTP方法,则可以使用“ *”作为值(对于响应标头,您仍然必须是显式的)。

除了在方法级别应用EnableCors属性之外,您还可以在类级别或全局将其应用于应用程序。应用属性的级别将为Web API代码中该级别及更低级别的所有请求配置CORS。因此,例如,如果在方法级别应用,则该策略将仅应用于对该操作的请求,而如果在类级别应用,则该策略将针对对该控制器的所有请求。最后,如果全局应用,则该策略将适用于所有请求。

以下是在类级别应用属性的另一个示例。此示例中使用的设置非常宽松,因为通配符用于允许的来源,请求标头和HTTP方法:

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

如果有多个位置的策略,则使用“最近”属性,而忽略其他属性(因此优先级是方法,然后是类,然后是全局)。如果您在较高级别上应用了该策略,但又希望在较低级别上排除了请求,则可以使用另一个名为DisableCorsAttribute的属性类。本质上,此属性是一项不允许权限的策略。

如果您不想在控制器上使用其他方法来允许CORS,则可以使用以下两种方法之一。首先,您可以在HTTP方法列表中显式显示,如图3 所示。或者,您可以保留通配符,但是使用DisableCors属性排除Delete方法,如图4 所示。

图3对HTTP方法使用显式值

[EnableCors("*", "*", "PUT, POST")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because DELETE is not in the method list above
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

图4使用DisableCors属性

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because of the [DisableCors] attribute
  [DisableCors]
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

CorsMessageHandler 必须为CORS框架启用CorsMessageHandler才能执行其拦截请求的工作,以评估CORS策略并发出CORS响应标头。通常,通过调用EnableCors扩展方法,在应用程序的Web API配置类中启用消息处理程序:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
  }
}

如果希望提供全局CORS策略,则可以将EnableCorsAttribute类的实例作为参数传递给EnableCors方法。例如,以下代码将在应用程序中全局配置许可的CORS策略:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
  }
}

与任何消息处理程序一样,CorsMessageHandler也可以按路由而不是全局注册。

对于ASP.NET Web API 2中的基本“开箱即用” CORS框架就是这样。关于该框架的一件好事是,它可扩展用于更动态的场景,下面我将介绍。 / p>

自定义政策

从前面的示例中可以明显看出,来源列表(如果未使用通配符)是编译为Web API代码的静态列表。尽管这可能在开发期间或在特定情况下可行,但仅需要动态确定来源列表(或其他权限)(例如,从数据库中)是不够的。

幸运的是,Web API中的CORS框架是可扩展的,因此支持动态来源列表很容易。实际上,该框架是如此灵活,以至于有两种通用的方法可以自定义策略的生成。

自定义CORS策略属性:启用动态CORS策略的一种方法是开发可从某些数据源生成策略的自定义属性类。可以使用此自定义属性类代替Web API提供的EnableCorsAttribute类。这种方法很简单,并且保留了细化的感觉,可以根据需要将属性应用于特定的类和方法(而不应用于其他类)。

要实现此方法,您只需构建类似于现有EnableCorsAttribute类的自定义属性。主要焦点是ICorsPolicyProvider接口,该接口负责为任何给定请求创建CorsPolicy的实例。图5包含一个示例。

图5自定义CORS策略属性

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = false)]
public class EnableCorsForPaidCustomersAttribute :
  Attribute, ICorsPolicyProvider
{
  public async Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    if (await IsOriginFromAPaidCustomer(originRequested))
    {
      // Grant CORS request
      var policy = new CorsPolicy
      {
        AllowAnyHeader = true,
        AllowAnyMethod = true,
      };
      policy.Origins.Add(originRequested);
      return policy;
    }
    else
    {
      // Reject CORS request
      return null;
    }
  }
  private async Task<bool> IsOriginFromAPaidCustomer(
    string originRequested)
  {
    // Do database look up here to determine if origin should be allowed
    return true;
  }
}

CorsPolicy类具有用于表达授予的CORS权限的所有属性。这里使用的值只是一个示例,但是大概可以从数据库查询(或任何其他来源)中动态填充它们。

自定义策略提供程序工厂:构建动态CORS策略的第二种通用​​方法是创建自定义策略提供程序工厂。这是CORS框架的一部分,可为当前请求获取策略提供者。 Web API的默认实现使用自定义属性来发现策略提供者(如前所述,属性类本身就是策略提供者)。这是CORS框架的另一个可插入部分,如果您想对自定义属性以外的策略使用某种方法,则可以实现自己的策略提供程序工厂。

前面描述的基于属性的方法提供了从请求到策略的隐式关联。定制策略提供程序工厂方法不同于属性方法,因为它要求您的实现提供逻辑以将传入的请求与策略进行匹配。这种方法更粗糙,因为它实际上是获得CORS政策的集中式方法。

图6 显示了自定义策略提供程序工厂的外观示例。此示例中的主要焦点是ICorsPolicyProviderFactory接口及其GetCorsPolicyProvider方法的实现。

Figure 6 A Custom Policy Provider Factory
public class DynamicPolicyProviderFactory : ICorsPolicyProviderFactory
{
  public ICorsPolicyProvider GetCorsPolicyProvider(
    HttpRequestMessage request)
  {
    var route = request.GetRouteData();
    var controller = (string)route.Values["controller"];
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    var policy = GetPolicyForControllerAndOrigin(
      controller, originRequested);
    return new CustomPolicyProvider(policy);
  }
  private CorsPolicy GetPolicyForControllerAndOrigin(
   string controller, string originRequested)
  {
    // Do database lookup to determine if the controller is allowed for
    // the origin and create CorsPolicy if it is (otherwise return null)
    var policy = new CorsPolicy();
    policy.Origins.Add(originRequested);
    policy.Methods.Add("GET");
    return policy;
  }
}
public class CustomPolicyProvider : ICorsPolicyProvider
{
  CorsPolicy policy;
  public CustomPolicyProvider(CorsPolicy policy)
  {
    this.policy = policy;
  }
  public Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    return Task.FromResult(this.policy);
  }
}

此方法的主要区别在于,完全取决于实施来根据传入请求确定策略。在图6中,控制器和来源可用于向数据库查询策略值。同样,此方法是最灵活的,但可能需要更多工作才能根据请求确定策略。

要使用自定义策略提供程序工厂,必须通过Web API配置中的SetCorsPolicyProviderFactory扩展方法向Web API注册它:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
    config.SetCorsPolicyProviderFactory(
      new DynamicPolicyProviderFactory());
  }
}

调试CORS

如果(和何时)您的跨域AJAX调用不起作用,则会想到一些调试CORS的技术。

客户端一种调试方法是简单地使用您选择的HTTP调试器(例如Fiddler)并检查所有HTTP请求。有了先前收集到的有关CORS规范详细信息的知识,您通常可以通过检查CORS HTTP标头(或缺少标头)来找出为什么未授予特定AJAX请求权限的原因。

另一种方法是使用浏览器的F12开发人员工具。当现代的浏览器中的控制台窗口由于CORS而导致AJAX调用失败时,会提供有用的错误消息。

服务器端,CORS框架本身使用Web API的跟踪功能提供详细的跟踪消息。只要向Web API注册了ITraceWriter,CORS框架就会发出消息,其中包含有关所选策略提供者,使用的策略以及发出的CORS HTTP标头的信息。有关Web API跟踪的更多信息,请查阅MSDN上的Web API文档。


我想强调的是,以上摘录不是我的工作,而完全归因于thinktecture的Brock Allen。