原文:http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api

Browser security prevents a web page from making AJAX requests to another domain. This restriction is called the same-origin policy, and prevents a malicious site from reading sentitive data from another site. However, sometimes you might want to let other sites call your web API.

Cross Origin Resource Sharing (CORS) is a W3C standard that allows a server to relax the same-origin policy. Using CORS, a server can explicitly allow some cross-origin requests while rejecting others. CORS is safer and more flexible than earlier techniques such as JSONP. This tutorial shows how to enable CORS in your Web API application.

本教程演示了 CORS 在 ASP.NET Web API 的支持。我们将开始通过创建两个 ASP.NET 项目 — — 一个被称为"web 服务",它承载 Web API 控制器和其他被称为"WebClient",调用 web 服务。因为两个应用程序驻留在不同的域,从 WebClient 到 web 服务的 AJAX 请求是跨起源的请求。

"同源"是什么?

如果他们有相同的方案、 主机和端口,两个 Url 具有相同的起源.

这两个网址具有相同的起源:

这些 Url 有不同起源比前两个:

 

  • http://example.net-不同的域
  • http://example.com:9000/foo.html -不同的端口
  • https://example.com/foo.html -不同的方案
  • http://www.example.com/foo.html -不同的子域

 

比较起源时,Internet Explorer 并不认为该端口。

 

创建 web 服务项目

 

启动 Visual Studio 时,创建一个新的ASP.NET Web 应用程序项目。选择项目模板。在"添加文件夹和核心的参考文件"下选择Web API复选框。(可选) 选择"主机在云"选项可将应用程序部署到微软 Azure。

 

 

添加 Web API 控制器命名为TestController ,用下面的代码:

 

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 API 工作,请导航到http://hostname/api/test/,其中主机名是你在那里部署了应用程序域。您应该看到响应文本,"得到: 测试消息"。

 

 

创建 WebClient 项目

 

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

 

 

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

 

<div>
    <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>
</div>

@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);
        }).error(function (jqXHR, textStatus, errorThrown) {
            $('#value1').text(jqXHR.responseText || textStatus);
        });
    }
</script>
}

 

 

对于serviceUrl变量,使用 web 服务应用程序的 URI。现在 WebClient 应用程序在本地运行,或将其发布到另一个网站。

 

点击"尝试它"按钮提交到 web 服务应用程序,使用中列出的 HTTP 方法一个 AJAX 请求下拉框 (GET,开机自检或付诸表决)。这让我们检查跨起源的不同要求。现在,web 服务应用程序不支持 CORS,所以如果您单击按钮,你会得到一个错误。

 

 

如果你看在像提琴手工具中的 HTTP 流量,您将看到浏览器会发送 GET 请求,并请求成功,但 AJAX 调用返回一个错误。它是重要的是理解同源策略不能防止浏览器从发送请求。相反,它可以防止应用程序看到的反应.

 

 

启用 CORS

 

现在让我们在 web 服务应用程序中启用 CORS。首先,添加 CORS NuGet 程序包。在 Visual Studio 中,从工具菜单中,选择库程序包管理器,然后选择程序包管理器控制台在程序包管理器控制台窗口中,键入以下命令:

 

Install-Package Microsoft.AspNet.WebApi.Cors

 

此命令安装最新的程序包,并更新所有的依赖关系,包括核心 Web API 库。用户-版本标记要针对特定的版本。CORS 软件包要求 Web 2.0 或更高版本的 API。

 

打开的文件 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 的末尾。

 

重新部署更新的 web 服务应用程序。您不需要更新 WebClient。现在从 WebClient AJAX 请求应该成功。所有允许使用 GET,付诸表决和 POST 方法。

 

 

CORS 的工作原理

 

本节介绍了 CORS 请求,一级的 HTTP 消息中会发生什么。它是重要的是理解如何 CORS 的工作,以便你可以正确地配置[EnableCors]属性及故障排除如果事情不按预期方式工作。

 

CORS 规范引入了几个新的 HTTP 标头,使跨起源的请求。如果浏览器支持 CORS,它将设置这些标题自动申请跨起源 ;你不需要做什么特别的 JavaScript 代码。

 

这里是跨起源请求的示例。"起源"头给出的发出请求的站点的域。

 

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

 

如果服务器允许该请求,它将设置访问控制允许源标题。此标头的值匹配产地标头,或者是通配符值"*",允许任何起源的意义。

 

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

 

如果响应中不包括访问控制允许起源页眉,AJAX 请求失败。具体来说,浏览器不允许该请求。即使服务器返回到成功的响应,浏览器不会响应客户端应用程序可用。

 

印前检查的请求

 

对于一些 CORS 的请求,由浏览器发送额外的请求,被称为"印前检查的请求"之前它实际为发送请求的资源。

 

如果下列条件为真,浏览器可以跳过的印前检查的请求:

 

  • 请求方法是 GET,头或 POST,
  • 应用程序未设置任何除了接受,接受语言内容语言的请求标头的内容类型,或最后一个事件 ID,
  • 内容类型标头 (如果设置) 是下列之一:
    • 应用程序/x-www-窗体-urlencoded
    • 多部分/格式数据
    • 文本

 

关于请求标头的规则适用于应用程序设置通过在客户端对象上调用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 选项的方法。它包括两个特殊标头:

 

  • 访问-控制-请求-方法: 将用于实际请求的 HTTP 方法。
  • 访问-控制-请求-标题: 在实际的请求设置应用程序的请求标头的列表。(再一次,这不包括由浏览器设置的页眉。)

 

这里是一个示例反应,假设服务器允许该请求:

 

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

 

该响应包括列出允许的方法中,一个访问控制允许方法头和 (可选) 一个访问控制允许标题标头,列出允许的标头。如果印前检查请求成功,浏览器发送的实际要求,如前面所述。

 

[EnableCors] 的范围规则

 

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

 

每个操作

 

若要启用 CORS 为单个操作,在操作方法上设置[EnableCors]属性。下面的示例启用 CORS GetItem只为方法。

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]属性添加到行动。下面的示例启用 CORS 除了PutItem的每个方法.

 

[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);
        // ...
    }
}

 

 

如果您在多个作用域设置了该属性,优先级的顺序为:

 

  1. 行动
  2. 控制器
  3. 全球

 

设置允许的起源

 

[EnableCors]属性的起源参数指定起源被允许访问该资源。价值是一个逗号分隔的列表,允许的起源。

 

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

 

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

 

在允许来自任何来源的请求之前仔细考虑。这意味着从字面上任何网站可以让您的网站的 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() { ... }    
}

 

 

设置允许的请求标头

 

早些时候我描述如何印前检查的请求可能包括访问控制请求标题标题清单由应用程序设置的 HTTP 标头 (所谓"作者请求标头")。[EnableCors]属性的标头参数指定允许哪些作者请求标头。若要允许任何标头,将标题设置为"*"。到白名单中特定的标头设置为一个逗号分隔的列表,允许标题的标题

 

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

 

然而,浏览器并不在他们如何设置访问控制请求标题完全一致。例如,Chrome 目前包括的"原点";尽管火狐浏览器不包括标准头文件,如"接受",即使应用程序在脚本中设置它们。

 

如果您将标题设置任何其他比"*",你应该至少包括"接受","内容类型",和"起源",再加上任何您想要支持的自定义标头。

 

设置允许的响应标头

 

默认情况下,浏览器未公开所有对应用程序的响应标头。默认情况下可用的响应标头是:

 

  • 缓存控制
  • 内容语言
  • 内容类型
  • 过期
  • 最后修订日期
  • 杂注

 

CORS 规格要求这些简单响应标头要使其他标头对应用程序可用,将[EnableCors] exposedHeaders参数设置.

 

在以下示例中,控制器的Get方法设置自定义标题命名为 ' X-自定义-头 '。默认情况下,浏览器将不公开此跨起源请求中的标头。若要使标题可用,包括exposedHeaders ' X-自定义-头 '.

 

[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 请求中的特殊处理。默认情况下,浏览器并不发送任何凭据与跨来源请求。凭据包括饼干,以及 HTTP 身份验证方案。若要发送凭据与跨起源请求,客户端必须将XMLHttpRequest.withCredentials设置为 true。

 

直接使用客户端代码

 

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 允许跨起源凭据, SupportsCredentials属性设为 true 的[EnableCors]属性上:

 

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

 

如果此属性为 true,HTTP 响应将包含访问控制允许凭据头。此标头告诉浏览器服务器允许跨起源请求的凭据。

 

如果浏览器发送凭据,但响应中不包括有效的访问控制允许凭据头,浏览器将不公开响应应用程序,而且 AJAX 请求就会失败。

 

要将SupportsCredentials设置为 true,关于非常小心,因为它意味着在另一个域的网站可以登录的用户凭据发送到您 Web API 以用户的名义,不在用户不知情的情况下。CORS 规范还规定,设置起源到"*"是无效的如果SupportsCredentials为 true。

 

自定义 CORS 政策提供商

 

[EnableCors]属性实现ICorsPolicyProvider接口。您可以通过创建一个类属性从派生并实现ICorsProlicyProvider提供您自己的实现.

 

[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对象,创建ICorsPolicyProvider对象。

 

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 的支持.

 

posted on 2014-12-23 18:43  PhotonInfo  阅读(1606)  评论(0)    收藏  举报