作者:Mike Wasson

此内容适用于早期版本的 .NET。 新开发应使用 ASP.NET Core 。 有关在 ASP.NET Core 中使用 Web API 和跨源请求 (CORS) 的详细信息,请参阅:

  • 教程:使用 ASP.NET Core 创建 Web API
  • 在 ASP.NET Core 中启用跨源请求 (CORS)
  • 浏览器安全性将阻止网页向另一个域发出 AJAX 请求。 此限制称为“同源策略”,可防止恶意站点从另一站点读取敏感数据。 但有时你可能想让其他站点调用你的 Web API。

    跨源资源共享 (CORS) 是一种 W3C 标准,允许服务器放宽同源策略。 使用 CORS,服务器可以显式允许某些跨域请求,同时拒绝另一些跨域请求。 CORS 比早期技术(如 JSONP )更安全、更灵活。 本教程演示如何在 Web API 应用程序中启用 CORS。

    本教程中使用的软件

  • Visual Studio
  • Web API 2.2
  • 本教程演示了 ASP.NET Web API 中的 CORS 支持。 首先,我们将创建两个 ASP.NET 项目-一个名为“WebService”,一个名为“WebService”,后者承载 Web API 控制器,另一个称为“WebClient”,后者调用 WebService。 由于这两个应用程序托管在不同的域中,因此从 WebClient 到 WebService 的 AJAX 请求是跨域请求。

    什么是“同一来源”?

    如果两个 URL 具有相同的方案、主机和端口,则其源相同。 ( RFC 6454 )

    这两个 URL 同源:

  • http://example.com/foo.html
  • http://example.com/bar.html
  • 这些 URL 的起源与前两个 URL 不同:

  • http://example.net - 不同的域
  • http://example.com:9000/foo.html - 不同的端口
  • https://example.com/foo.html - 不同的方案
  • http://www.example.com/foo.html - 不同的子域
  • 比较源时,Internet Explorer 不考虑端口。

    创建 WebService 项目

    本部分假设你已了解如何创建 Web API 项目。 如果没有,请参阅 具有 ASP.NET Web API的入门

  • 启动 Visual Studio 并创建新的 ASP.NET Web 应用程序 (.NET Framework) 项目。

  • 在“ 新建 ASP.NET Web 应用程序 ”对话框中,选择“ 项目模板”。 在 “添加文件夹和核心引用” 下,选中 “Web API ”复选框。

  • 添加包含以下代码的 TestController Web API 控制器:

    using System.Net.Http;
    using System.Web.Http;
    namespace WebService.Controllers
        public class TestController : ApiController
            public HttpResponseMessage Get()
                return new HttpResponseMessage()
                    Content = new StringContent("GET: Test message")
            public HttpResponseMessage Post()
                return new HttpResponseMessage()
                    Content = new StringContent("POST: Test message")
            public HttpResponseMessage Put()
                return new HttpResponseMessage()
                    Content = new StringContent("PUT: Test message")
    
  • 可以在本地运行应用程序或部署到 Azure。 (对于本教程中的屏幕截图,应用将部署到 Azure 应用服务 Web 应用.) 若要验证 Web API 是否正常工作,请http://hostname/api/test/导航到主机名是部署应用程序的域。 应会看到响应文本“GET: 测试消息”。

    创建 WebClient 项目

  • 创建另一个 ASP.NET Web 应用程序 (.NET Framework) 项目并选择 MVC 项目模板。 (可选)选择“更改身份验证无身份验证>”。 本教程不需要身份验证。

  • 解决方案资源管理器中,打开文件 Views/Home/Index.cshtml。 将此文件中的代码替换为以下内容:

    <select id="method"> <option value="get">GET</option> <option value="post">POST</option> <option value="put">PUT</option> </select> <input type="button" value="Try it" onclick="sendRequest()" /> <span id='value1'>(Result)</span> @section scripts { <script> // TODO: Replace with the URL of your WebService app var serviceUrl = 'http://mywebservice/api/test'; function sendRequest() { var method = $('#method').val(); $.ajax({ type: method, url: serviceUrl }).done(function (data) { $('#value1').text(data); }).fail(function (jqXHR, textStatus, errorThrown) { $('#value1').text(jqXHR.responseText || textStatus); </script>

    对于 serviceUrl 变量,请使用 WebService 应用的 URI。

  • 在本地运行 WebClient 应用或将其发布到其他网站。

    单击“试用”按钮时,将使用下拉列表中列出的 HTTP 方法将 AJAX 请求提交到 WebService 应用, (GET、POST 或 PUT) 。 这样,便可以检查不同的跨域请求。 目前,WebService 应用不支持 CORS,因此,如果单击该按钮,将收到错误。

    如果在 Fiddler 等工具中观察 HTTP 流量,则会看到浏览器确实发送 GET 请求,请求成功,但 AJAX 调用返回错误。 请务必了解同源策略不会阻止浏览器 发送 请求。 相反,它会阻止应用程序查看 响应

    启用 CORS

    现在,让我们在 WebService 应用中启用 CORS。 首先,添加 CORS NuGet 包。 在 Visual Studio 的 “工具” 菜单中,选择 “NuGet 包管理器”,然后选择“ 包管理器控制台”。 在“包管理器控制台”窗口中,键入以下命令:

    Install-Package Microsoft.AspNet.WebApi.Cors
    

    此命令将安装最新的包并更新所有依赖项,包括核心 Web API 库。 使用 -Version 标志以特定版本为目标。 CORS 包需要 Web API 2.0 或更高版本。

    打开 文件App_Start/WebApiConfig.cs。 将以下代码添加到 WebApiConfig.Register 方法:

    using System.Web.Http;
    namespace WebService
        public static class WebApiConfig
            public static void Register(HttpConfiguration config)
                // New code
                config.EnableCors();
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
    

    接下来,将 [EnableCors] 属性添加到 TestController 类:

    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Http.Cors;
    namespace WebService.Controllers
        [EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
        public class TestController : ApiController
            // Controller methods not shown...
    

    对于 参数,请使用在其中部署 WebClient 应用程序的 URI。 这允许来自 WebClient 的跨域请求,同时仍然禁止所有其他跨域请求。 稍后,我将更详细地描述 [EnableCors] 的参数。

    不要在 URL 末尾包含正斜杠。

    重新部署更新的 WebService 应用程序。 无需更新 WebClient。 现在,来自 WebClient 的 AJAX 请求应成功。 允许使用 GET、PUT 和 POST 方法。

    CORS 的工作原理

    本部分介绍在 HTTP 消息级别 CORS 请求中发生的情况。 请务必了解 CORS 的工作原理,以便可以正确配置 [EnableCors] 属性,并根据需要进行故障排除。

    CORS 规范引入了几个启用跨源请求的新 HTTP 标头。 如果浏览器支持 CORS,则会自动为跨域请求设置这些标头;无需在 JavaScript 代码中执行任何特殊操作。

    下面是跨域请求的示例。 “Origin”标头为发出请求的网站提供域。

    GET http://myservice.azurewebsites.net/api/test HTTP/1.1
    Referer: http://myclient.azurewebsites.net/
    Accept: */*
    Accept-Language: en-US
    Origin: http://myclient.azurewebsites.net
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
    Host: myservice.azurewebsites.net
    

    如果服务器允许请求,它将设置 Access-Control-Allow-Origin 标头。 此标头的值与 Origin 标头匹配,或者是通配符值“*”,表示允许任何源。

    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: text/plain; charset=utf-8
    Access-Control-Allow-Origin: http://myclient.azurewebsites.net
    Date: Wed, 05 Jun 2013 06:27:30 GMT
    Content-Length: 17
    GET: Test message
    

    如果响应不包含 Access-Control-Allow-Origin 标头,AJAX 请求将失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会向客户端应用程序提供响应。

    对于某些 CORS 请求,浏览器会在发送资源的实际请求之前发送一个名为“预检请求”的其他请求。

    如果满足以下条件,浏览器可以跳过预检请求:

  • 请求方法为 GET、HEAD 或 POST, 以及

  • 应用程序不设置除 Accept、Accept-Language、Content-Language、Content-Type 或 Last-Event-ID 以外的任何请求 标头,

  • 如果设置) 为下列项之一,则 Content-Type 标头 (:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain
  • 有关请求标头的规则适用于应用程序通过在 XMLHttpRequest 对象上调用 setRequestHeader 设置的标头。 (CORS 规范调用这些“作者请求标头”。) 浏览器可以设置的标头不适用于 浏览器 可以设置的标头,例如用户代理、主机或内容长度。

    下面是预检请求的示例:

    OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
    Accept: */*
    Origin: http://myclient.azurewebsites.net
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: accept, x-my-custom-header
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
    Host: myservice.azurewebsites.net
    Content-Length: 0
    

    预外部测试版请求使用 HTTP OPTIONS 方法。 它包括两个特殊标头:

  • Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
  • Access-Control-Request-Headers: 应用程序 在实际请求上设置的请求标头列表。 再次 (,这不包括浏览器设置的标头。)
  • 下面是一个示例响应,假设服务器允许请求:

    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 0
    Access-Control-Allow-Origin: http://myclient.azurewebsites.net
    Access-Control-Allow-Headers: x-my-custom-header
    Access-Control-Allow-Methods: PUT
    Date: Wed, 05 Jun 2013 06:33:22 GMT
    

    响应包括一个 Access-Control-Allow-Methods 标头,该标头列出允许的方法,以及一个 Access-Control-Allow-Headers 标头,其中列出了允许的标头。 如果预检请求成功,浏览器会发送实际请求,如前所述。

    通常用于测试预检 OPTIONS 请求的终结点的工具 (,例如 FiddlerPostman) 默认情况下不会发送所需的 OPTIONS 标头。 确认 Access-Control-Request-Method 使用请求发送和 Access-Control-Request-Headers 标头,并且 OPTIONS 标头通过 IIS 到达应用。

    若要将 IIS 配置为允许 ASP.NET 应用接收和处理 OPTION 请求,请在部分中将以下配置添加到应用的 web.config 文件中 <system.webServer><handlers>

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

    删除 OPTIONSVerbHandler 可防止 IIS 处理 OPTIONS 请求。 ExtensionlessUrlHandler-Integrated-4.0替换允许 OPTIONS 请求访问应用,因为默认模块注册仅允许使用无扩展 URL 的 GET、HEAD、POST 和 DEBUG 请求。

    [EnableCors] 的范围规则

    可以为应用程序中的所有 Web API 控制器启用每个操作、每个控制器或全局 CORS。

    若要为单个操作启用 CORS,请对操作方法设置 [EnableCors] 属性。 以下示例仅为 GetItem 该方法启用 CORS。

    public class ItemsController : ApiController
        public HttpResponseMessage GetAll() { ... }
        [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
        public HttpResponseMessage GetItem(int id) { ... }
        public HttpResponseMessage Post() { ... }
        public HttpResponseMessage PutItem(int id) { ... }
    

    每个控制器

    如果在控制器类上设置了 [EnableCors] ,则它适用于控制器上的所有操作。 若要禁用操作的 CORS,请将 [DisableCors] 属性添加到该操作。 以下示例为除以下 PutItem各项方法启用 CORS。

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public class ItemsController : ApiController
        public HttpResponseMessage GetAll() { ... }
        public HttpResponseMessage GetItem(int id) { ... }
        public HttpResponseMessage Post() { ... }
        [DisableCors]
        public HttpResponseMessage PutItem(int id) { ... }
    

    若要为应用程序中的所有 Web API 控制器启用 CORS,请将 EnableCorsAttribute 实例传递给 EnableCors 方法:

    public static class WebApiConfig
        public static void Register(HttpConfiguration config)
            var cors = new EnableCorsAttribute("www.example.com", "*", "*");
            config.EnableCors(cors);
            // ...
    

    如果在多个范围内设置属性,则优先级顺序为:

    设置允许的源

    [EnableCors] 属性的参数指定允许哪些源访问资源。 该值是允许的源的逗号分隔列表。

    [EnableCors(origins: "http://www.contoso.com,http://www.example.com", 
        headers: "*", methods: "*")]
    

    还可以使用通配符值“*”允许来自任何源的请求。

    在允许来自任何源的请求之前,请仔细考虑。 这意味着,任何网站都可以对 Web API 进行 AJAX 调用。

    // Allow CORS for all origins. (Caution!)
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    

    设置允许的 HTTP 方法

    [EnableCors] 属性的方法参数指定允许哪些 HTTP 方法访问资源。 若要允许所有方法,请使用通配符值“*”。 以下示例仅允许 GET 和 POST 请求。

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
    public class TestController : ApiController
        public HttpResponseMessage Get() { ... }
        public HttpResponseMessage Post() { ... }
        public HttpResponseMessage Put() { ... }    
    

    设置允许的请求头

    本文前面介绍了预检请求如何包括 Access-Control-Request-Headers 标头,其中列出了应用程序设置的 HTTP 标头, (所谓的“作者请求标头”) 。 [EnableCors] 属性的标头参数指定允许哪些作者请求标头。 若要允许任何标头,请将 标头 设置为“*”。 若要允许特定标头,请将 标头 设置为允许标头的逗号分隔列表:

    [EnableCors(origins: "http://example.com", 
        headers: "accept,content-type,origin,x-my-header", methods: "*")]
    

    但是,浏览器在设置 Access-Control-Request-Header 的方式中并不完全一致。 例如,Chrome 当前包含“origin”。 FireFox 不包括标准标头(如“Accept”),即使应用程序在脚本中设置它们也是如此。

    如果将 标头 设置为除“*”以外的任何内容,则应至少包括“accept”、“content-type”和“origin”,以及要支持的任何自定义标头。

    设置允许的响应标头

    默认情况下,浏览器不会向应用程序公开所有响应标头。 默认情况下可用的响应头包括:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma
  • CORS 规范调用这些 简单的响应标头。 若要使其他标头可用于应用程序,请设置 [EnableCors]公开Headers 参数。

    在以下示例中,控制器的方法设置名为“X-Custom-Header”的 Get 自定义标头。 默认情况下,浏览器不会在跨源请求中公开此标头。 若要使标头可用,请在 公开的Headers 中包含“X-Custom-Header”。

    [EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
    public class TestController : ApiController
        public HttpResponseMessage Get()
            var resp = new HttpResponseMessage()
                Content = new StringContent("GET: Test message")
            resp.Headers.Add("X-Custom-Header", "hello");
            return resp;
    

    在跨源请求中传递凭据

    凭据需要在 CORS 请求中进行特殊处理。 默认情况下,浏览器不会发送具有跨源请求的任何凭据。 凭据包括 Cookie 以及 HTTP 身份验证方案。 若要发送具有跨域请求的凭据,客户端必须将 XMLHttpRequest.withCredentials 设置为 true。

    直接使用 XMLHttpRequest

    var xhr = new XMLHttpRequest();
    xhr.open('get', 'http://www.example.com/api/test');
    xhr.withCredentials = true;
    

    在 jQuery 中:

    $.ajax({
        type: 'get',
        url: 'http://www.example.com/api/test',
        xhrFields: {
            withCredentials: true
    

    此外,服务器必须允许凭据。 若要在 Web API 中允许跨域凭据,请将 [EnableCors] 属性上的 SupportsCredentials 属性设置为 true:

    [EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", 
        methods: "*", SupportsCredentials = true)]
    

    如果此属性为 true,HTTP 响应将包含 Access-Control-Allow-Credentials 标头。 此标头告知浏览器服务器允许跨源请求的凭据。

    如果浏览器发送凭据,但响应不包括有效的 Access-Control-Allow-Credentials 标头,则浏览器不会向应用程序公开响应,并且 AJAX 请求失败。

    请务必注意将 SupportsCredentials 设置为 true,因为这意味着另一个域中的网站可以代表用户将登录用户的凭据发送到 Web API,而无需用户知道。 CORS 规范还指出,如果 SupportsCredentials 为 true,则将设置为“*”无效。

    自定义 CORS 策略提供程序

    [EnableCors] 属性实现 ICorsPolicyProvider 接口。 可以通过创建派生自 Attribute 并实现 ICorsPolicyProvider 的类来提供自己的实现。

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
    public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
        private CorsPolicy _policy;
        public MyCorsPolicyAttribute()
            // Create a CORS policy.
            _policy = new CorsPolicy
                AllowAnyMethod = true,
                AllowAnyHeader = true
            // Add allowed origins.
            _policy.Origins.Add("http://myclient.azurewebsites.net");
            _policy.Origins.Add("http://www.contoso.com");
        public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
            return Task.FromResult(_policy);
    

    现在,你可以应用将 放置 [EnableCors] 的任何位置的属性。

    [MyCorsPolicy]
    public class TestController : ApiController
        .. //
    

    例如,自定义 CORS 策略提供程序可以从配置文件读取设置。

    作为使用属性的替代方法,可以注册创建 ICorsPolicyProviderFactory 对象的 ICorsPolicyProviderFactory 对象。

    public class CorsPolicyFactory : ICorsPolicyProviderFactory
        ICorsPolicyProvider _provider = new MyCorsPolicyProvider();
        public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
            return _provider;
    

    若要设置 ICorsPolicyProviderFactory,请在启动时调用 SetCorsPolicyProviderFactory 扩展方法,如下所示:

    public static class WebApiConfig
        public static void Register(HttpConfiguration config)
            config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
            config.EnableCors();
            // ...
    

    浏览器支持

    Web API CORS 包是服务器端技术。 用户的浏览器还需要支持 CORS。 幸运的是,所有主要浏览器的当前版本都包括 对 CORS 的支持

  •