简介:原文 ASP.NET Web API 2 对 CORS 的支持
ASP.NET Web API 2 对 CORS 的支持
Brock Allen
跨域资源共享 (CORS) 是一种万维网联合会 (W3C) 规范(通常被认为是 HTML5 的一部分),它可让 JavaScript 克服由浏览器施加的同域策略安全限制。
CORS 可让服务器指明允许哪些域对它们进行调用,从而放宽这种限制。CORS 是由浏览器强制执行的,并且必须在服务器上实现,而最新版本的 ASP.NET Web API 2 全面支持 CORS。通过 Web API 2,您可以对策略进行配置以允许不同域的 JavaScript 客户端访问您的 API。
CORS 基本信息
由于 Web API 完全按照该规范来实现,因此,为了使用 Web API 中的新 CORS 功能,详细了解 CORS 本身将大有帮助。这些详细内容现在看起来可能都是理论之谈,但对于以后了解 Web API 中的可用设置来说将十分有用:在您调试 CORS 时,这些内容有助于您更快速地解决问题。
请注意,进行简单 CORS 请求时,仍会对服务器进行调用。如果您对 CORS 了解不深,可能会觉得奇怪,但这种行为无异于浏览器已构造 <form> 元素并进行正常 POST 请求的情况。CORS 不会阻止对服务器的调用;但它会阻止调用 JavaScript 接收结果。如果您要阻止调用方调用服务器,则需要在服务器代码中实现某种授权(可能要使用 [Authorize] 授权筛选器属性)。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: content-typeAccess-Control-Max-Age: 600
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912Access-Control-Expose-Headers: bar
bar: a bar value
Content-Length: 27
{"Value1":"foo","Value2":5}
如果使用 Authorization 标头而不使用 Cookie(例如,在使用基本或集成 Windows 身份验证时),则适用同样一组规则和行为。有关使用凭据和 Authorization 标头的注意事项:服务器不必在 Access-Control-Allow-Headers CORS 响应标头中显式授予 Authorization 标头。
请注意,在使用 Access-Control-Allow-Credentials CORS 响应标头时,如果服务器发出此标头,则“*”通配符值不能用于 Access-Control-Allow-Origin。相反,CORS 规范要求使用显式域。Web API 框架会为您处理这种情况,我之所以在此提及,是因为您可能会在调试期间注意到这种行为。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: authorization
在 Authorization 标头中显式设置令牌值是一种较为安全的身份验证方法,因为您可以避免遭受“跨网站请求伪造”(CSRF) 攻击。您可在 Visual Studio 2013 新增的单页应用程序 (SPA) 模板中看到这种方法。
至此,您已经了解了 HTTP 级别上的 CORS 基础知识,接下来,我将向您说明如何使用新的 CORS 框架从 Web API 发出这些标头。
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 类有一个可接受三个或四个参数的重载构造函数。这些参数(依次)为:
除在方法级别应用 EnableCors 属性外,还可以在类级别应用该属性,或将其全局应用于应用程序。应用该属性的级别会在 Web API 代码中为该级别及下面级别的所有请求配置 CORS。例如,如果在方法级别应用该策略,则该策略仅应用于该操作的请求,而如果在类级别应用该策略,则该策略将应用于对该控制器的所有请求。最后,如果全局应用该策略,则该策略将应用于所有请求。
[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 响应标头而拦截请求的工作。消息处理程序的启用通常是在应用程序的 Web API 配置类中通过调用 EnableCors 扩展方法进行的:
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("*", "*", "*"));
}
}
因此,这是 ASP.NET Web API 2 中的基本“开箱即用”CORS 框架。此框架的一个好处是,它可以针对更加动态的情况进行扩展,接下来我会对此进行介绍。
自定义策略
从前面的示例可明显看到,域列表(如果未使用通配符)是一个编译到 Web API 代码中的静态列表。虽然这样做在开发过程中或在特定情况下可能行得通,但如果需要动态确定域列表或其他权限(比如,从数据库获取),则静态列表就不够用了。
幸运的是,Web API 中的 CORS 框架是可扩展的,可以十分方便地支持动态域列表。实际上,该框架十分灵活,有两种常用的对策略生成进行自定义的方法。
自定义 CORS 策略属性。启用动态 CORS 策略的一种方法是开发一个可从某个数据源生成策略的自定义属性类。可以使用此自定义属性类,而不使用由 Web API 提供的 EnableCorsAttribute 类。这种方法十分简便,并且能够根据需要对特定类和方法(而不对其它类和方法)应用属性,从而保持控制的精细度。
[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 策略的集中式方法。
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);
}
}
若要使用该自定义策略提供程序工厂,必须通过 Web API 配置中的 SetCorsPolicyProviderFactory 扩展方法将其注册到 Web API:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Other configuration omitted
config.EnableCors();
config.SetCorsPolicyProviderFactory(
new DynamicPolicyProviderFactory());
}
}
持续的社区贡献
ASP.NET Web API 是一个开源框架,同时是更大的开源框架集(统称为 ASP.NET Web 堆栈)的一部分,该堆栈还包括 MVC、网页等。
这些框架用于生成 ASP.NET 平台,并由 Microsoft 的 ASP.NET 团队进行管理。作为开源平台的管理者,ASP.NET 团队欢迎社区贡献,而 Web API 中的跨域资源共享 (CORS) 实现就是一项贡献内容。
客户端。一种很简单的调试方法,就是使用自选 HTTP 调试器(如 Fiddler)并检查所有 HTTP 请求。借助前面搜集的有关 CORS 规范详细内容的知识,您通常可以通过检查 CORS HTTP 标头(或者是否缺少标头)找出没有为特定 AJAX 请求授予权限的原因。
另一种方法是使用浏览器的 F12 开发者工具。现代浏览器中的控制台窗口会在 AJAX 调用因 CORS 而失败时提供有用的错误消息。
服务器端。CORS 框架本身会使用 Web API 的跟踪功能来提供详细的跟踪消息。只要向 Web API 注册了 ITraceWriter,CORS 框架就会发出消息,其中包含有关所选择的策略提供程序、使用的策略以及发送的 CORS HTTP 标头的信息。有关 Web API 跟踪的详细信息,请参阅 MSDN 上的 Web API 文档。
需求极高的功能
一段时间以来,CORS 一直是一个需求极高的功能,并最终内置到 Web API 中。本文重点介绍了 CORS 自身的详细内容,这种知识在实现和调试 CORS 过程中十分重要。掌握了这种知识,您就能够方便地利用 Web API 中的 CORS 支持,在应用程序中实现跨域调用。
Brock Allen是一名顾问,专门从事 Microsoft .NET Framework、Web 开发和基于 Web 的安全性工作。他还是培训公司 DevelopMentor 的一名讲师、thinktecture GmbH & Co. KG 的助理顾问、thinktecture 开源项目的撰稿人以及 ASP.NET 平台的撰稿人。您可在他的网站brockallen.com 与他取得联系,或向他发送电子邮件,邮件地址为brockallen@gmail.com。
衷心感谢以下技术专家对本文的审阅:Yao Huan Lin (Microsoft) Yao Huang Lin (yaohuang@microsoft.com) 是 Microsoft 的 ASP.NET Web API 团队中的软件开发者。他参与了 .NET Framework 的许多组件的工作,包括 ASP.NET、Windows Communication Foundation (WCF) 和 Windows Workflow Foundation (WF)。