九、在 ASP.NET Web API 中启用跨源资源共享(CORS)

本章将帮助您学习如何在 Web API 应用中启用 CORS。

在本章中,我们将涵盖以下主题:

  • 歌珥是什么?
  • 歌珥是如何工作的
  • 设置允许的起源
  • 设置允许的 HTTP 方法
  • 设置允许的请求头
  • 设置允许的响应头
  • 在跨源请求中传递凭证
  • 在不同范围启用 CORS

什么是 CORS?

根据同源策略,浏览器安全性避免任何从一个域到另一个域的 Web API 的 AJAX 请求,以防止恶意站点读取敏感数据或将其发布到另一个站点。 但是,在某些情况下,您可能需要启用其他域来调用您的 Web API。 这就是 CORS 出现的原因。

跨源资源共享(CORS)允许服务器根据配置忽略同源策略。 CORS 使服务器能够提供对其资源的受限访问。

CORS 如何工作

跨源资源共享设计提供了多种 HTTP 报头,如 Origin 和 Access-Control-Allow-Origin。 这些头将由支持 CORS 的浏览器为跨源请求设置。

让我们尝试访问以下未配置为支持 CORS 的 Web API 方法:

// GET: api/Contacts/id
public Contact Get(int id)
{
      return contacts.FirstOrDefault(x => x.Id == id);
}

从不同的域访问此方法将导致以下错误:

XMLHttpRequest cannot load http://localhost:53858/api/contacts/1\. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:53870' is therefore not allowed access. 

我们需要将一些特殊的头(例如请求中的 Origin 头)传递给配置了 CORS 的 Web API 方法。 下面的代码片段展示了 Web API 中的一个这样的方法:

[EnableCors(origins: "http://localhost:53870", headers: "*", methods: "*")]
// GET: api/Contacts
public IEnumerable<Contact> Get()
{
    return contacts;
} 

要访问此方法,客户机需要在 Origin 头中传递它的域,以便 Web API 服务器对其进行验证。 下面是带有 Origin 头的 HTTP 请求示例,它包含发送此请求的域的信息:

GET http://localhost:53858/api/contacts HTTP/1.1
Host: localhost:53858
Connection: keep-alive
Accept: */*
Origin: http://localhost:53870
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36
Referer: http://localhost:53870/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6

来自支持 CORS 并允许请求的服务器的 HTTP 响应示例如下:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: http://localhost:53870
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 04 Sep 2015 05:08:14 GMT
Content-Length: 218

[{"Id":1,"Name":"Steve","Email":"steve@gmail.com","Mobile":"+1(234)35434"},{"Id":2,"Name":"Matt","Email":"matt@gmail.com","Mobile":"+1(234)5654"},{"Id":3,"Name":"Mark","Email":"mark@gmail.com","Mobile":"+1(234)56789"}]

正如您可以在前面的响应中看到的,Web API 服务器通过发送 Access-Control-Allow-Origin 报头来响应允许的域来确认客户端域。

设置允许的起源

我们需要在[EnableCors]属性的 origins 参数中提供以逗号分隔的域列表,如下代码片段所示:

[EnableCors(origins: " http://localhost:53870, http://localhost:53871", headers: "*", methods: "*")] 

如您所见,这个 CORS 配置只允许来自两个域(http://localhost:53870/http://localhost:53871/的 AJAX 请求,并拒绝来自其他域的任何请求。 我们也可以通过传递一个"*"通配符值来让 CORS 接受来自所有域的任何请求,如下所示:

[EnableCors(origins: "*", headers: "*", methods: "*")]

在将"*"通配符值应用到起源之前,最好重新考虑一下,因为它将允许任何域向您的 Web API 发出 AJAX 请求。

设置允许的 HTTP 方法

我们还可以限制 CORS 中的 HTTP 方法。 这可以通过为[EnableCors]属性的 methods 参数提供一个用逗号分隔的 HTTP 方法列表来实现,如下所示:

[EnableCors(origins: "http://localhost:53870", headers: "*", methods: "get,post")]
public class ContactsController : ApiController
    {
        IEnumerable<Contact> contacts = new List<Contact> 
        { 
            new Contact { Id = 1, Name = "Steve", Email = "steve@gmail.com", Mobile = "+1(234)35434" }, 
            new Contact { Id = 2, Name = "Matt", Email = "matt@gmail.com", Mobile = "+1(234)5654" }, 
            new Contact { Id = 3, Name = "Mark", Email = "mark@gmail.com", Mobile = "+1(234)56789" } 
        };

        // GET: api/Contacts
        public IEnumerable<Contact> Get()
        {
            return contacts;
        }

        // GET: api/Contacts/id
        public Contact Get(int id)
        {
            return contacts.FirstOrDefault(x => x.Id == id);
        }
    }

前面的代码只允许GETPOST HTTP方法。 我们还可以通过向方法传递"*"通配符值来允许所有HTTP方法。

设置允许的请求头

有时,为了验证 CORS,浏览器在发送实际请求之前发送一个先决条件。 这些先决条件将使用HTTP OPTIONS方法,并且请求将具有以下访问控制请求头:

  • Access-Control-Request-Method
  • Access-Control-Request-Headers

应用于实际请求的 HTTP 操作方法名称提供给 Access-Control-Request-Method,应用于实际请求的以逗号分隔的报头列表提供给 Access-Control-Request-Headers。 下面的示例请求就是这样一个先决条件:

OPTIONS http://localhost:53858/api/contacts HTTP/1.1
Host: localhost:53858
Accept: */*
Origin: http://localhost:53870
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)
Content-Length: 0

现在,服务器将响应关于它是否允许在实际请求的前提条件的特殊头中使用HTTP方法和头的信息。

在这里的先决条件中,浏览器询问服务器是否允许HTTP方法 PUT 以及 accept 和 x-my-custom-header 等请求头。 以下来自服务器的响应确认它可以允许所请求的方法和头:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://localhost:53870
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Sun, 30 Aug 2013 05:56:22 IST

如何配置服务器以允许某些请求头? 这将在[EnableCors]属性的headers参数中进行配置,如下所示:

[EnableCors(origins: "http://localhost:53870", 
    headers: "accept,content-type,origin,x-my-header", methods: "put")]

注意事项

注意,如果配置了任何特定的自定义头,那么最好至少包括“accept”、“content-type”和“origin”以及自定义头。

设置允许的响应头

  • 有一些在响应中可用的默认头文件,并且由浏览器提供。 这些默认头文件包括 Content-Type、Content-Language、Cache-Control、Expires、Pragma 和 Last-Modified。 这些被称为简单响应头。
  • 然而,在某些场景中,您可能希望在响应中公开一些特殊的头。 为了实现这一点,CORS 简化了[EnableCors]属性中名为exposedHeaders的参数。
  • 例如,让我们在响应中设置一个名为"X-Custom-Header"的特殊头。 由于这是一个特殊的头文件,默认情况下浏览器不会在跨源请求中公开它。 为了使浏览器能够公开这个特殊的头,我们需要在[EnableCors]属性的exposedHeaders参数中设置头"X-Custom-Header",如下代码片段所示:

在跨源请求中传递凭证

浏览器在跨源请求中默认不传递 cookie 和 HTTP 认证方案等凭证。 为了在来自客户端的跨源请求中传递凭证,客户端必须将XMLHttpRequest.withCredentials设置为 true,如下所示:

$.ajax({
    type: 'get',
    url: ' http://localhost:53858 /api/contacts,
    xhrFields: {
        withCredentials: true
    }

为了允许跨源请求中的凭据,应该将[EnableCors]属性上的SupportsCredentials属性设置为true,如下代码所示:

[EnableCors(origins: "http://chapter09client.com", headers: "*", methods: "*", SupportsCredentials = true)]

HTTP 响应还将具有 Access-Control-Allow-Credentials 报头,以指示浏览器,服务器可以在跨源请求中接受凭证。 使用 Cookie 或 Authorization 头,Web API 验证请求。 认证完成后,浏览器将继续在所有后续请求上向服务器传递认证信息。

注意事项

注意,我们不能将"*"通配符值设置为origins参数并启用它,以便同时支持凭据。

如果响应没有 Access-Control-Allow-Credentials 头,AJAX 调用方法将不会接收响应,因为浏览器没有公开它,这将导致 AJAX 请求失败。

在不同范围内启用 CORS

CORS 可以在不同的级别上启用。 我们可以在动作级别、控制器级别或全局级别设置 CORS。 让我们看看如何在不同的范围设置 CORS。

在动作级别启用

为了使 CORS 对特定的操作生效,我们需要用[EnableCors]属性装饰 action 方法,如下代码片段所示:

public class ContactsController : ApiController
{
    [EnableCors(origins: "http://localhost:53870", headers: "*", methods: "*")]
    public HttpResponseMessage GetContacts() { ... }

    public HttpResponseMessage GetContact(int id) { ... }
    public HttpResponseMessage PostContact() { ... }
    public HttpResponseMessage PutContact(int id) { ... }
}

如所示,CORS 仅适用于GetContacts()动作方法。

控制器级别启用

我们还可以在特定的控制器上启用 CORS。 我们只需要用[EnableCors]属性装饰控制器,如下:

[EnableCors(origins: "http://localhost:53870", headers: "*", methods: "*")]
public class ContactsController : ApiController
{    
    public HttpResponseMessage GetContacts() { ... }    
    public HttpResponseMessage GetContact(int id) { ... }
    public HttpResponseMessage PostContact() { ... }
    public HttpResponseMessage PutContact(int id) { ... }
}

如您所见,CORS 适用于此控制器中的任何操作。 然而,有时,您可能希望为一个特定操作禁用 CORS。 假设,我们想要禁用PutContact()操作方法的 CORS。 这可以很容易地通过使用[DisableCors]属性装饰PutContact()动作来实现,如下所示:

[EnableCors(origins: "http://chapter09client.com", headers: "*", methods: "*")]
public class ContactsController : ApiController
{    
    public HttpResponseMessage GetContacts() { ... }    
    public HttpResponseMessage GetContact(int id) { ... }
    public HttpResponseMessage PostContact() { ... }

    [DisableCors]
    public HttpResponseMessage PutContact(int id) { ... }
}

如果在控制器级别启用了 CORS,并且您希望为控制器中的特定方法禁用 CORS,那么我们可以使用DisableCors属性装饰这些方法,就像前面的代码片段中给出的那样。

全局启用 CORS

有时候,我们可能需要为 Web API 中的所有控制器和操作启用 CORS。 换句话说,我们可能需要在整个 Web API 中启用 CORS。

这可以通过全局启用 CORS 来实现。 我们需要在WebApiConfig文件中传递一个 HTTP 配置中的EnableCorsAttribute实例给EnableCors方法,如下所示:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("http://localhost:53870", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

这个全局配置可以在控制器或操作级别被覆盖。 优先级的顺序是动作、控制器和全局。

总结

哦吼! 您刚刚学习了如何在我们的 Web API 中启用跨源请求共享(CORS)。

你们了解了 CORS 是什么以及它是如何工作的。

然后您学习了一些配置内容,包括设置允许的起源、HTTP方法、请求头和响应头。

最后,您了解了在跨源请求中传递凭据以及在 Web API 的不同范围中启用 CORS。

华友世纪! 就是这样,人! 现在,我们知道如何通过采用市场上各种技术中的 apt 安全解决方案来保护我们的 Web API。