使用CORS:跨域两三事

本文为译文。

简介

    APIS是可以将富网页应用串连在一起的线程。但是这个应用难以转给浏览器,跨域请求技术的选择被限制了,类似JSONP(由于安全考虑,使用会被限制),或者配置代理(设置和维护都比较头痛)。

    Cross-Origin Resource Sharing(CORS)是允许来自浏览器的跨域通信的W3C规范。通过设置XMLHttpRequest的头部,CORS允许开发者使用类似同域中请求惯用的方法。

    CORS的用法很简单,假设网站alice.com有一些bob.com想要获取的数据。这类型的请求以前通常在浏览器的同源策略下不被允许。但是,借助CORS请求,alice.com可以加一些特殊回应头信息允许bob.com取得数据。

    可以从这个例子里面看出来,支持CORS需要服务器和客户端之间的协调。幸运的是,如果你是客户端开发者,你可以屏蔽掉这中间大多数的细节。这篇文章余下的部分会展示客户端如何进行跨域请求,及服务器如何配置来支持CORS。

发起CORS请求

    这部分介绍如何使用javascript发起CORS请求。

    创建XMLHttpRequest请求:

    下列浏览器支持CORS:

    Chrome 3+

    Firefox 3.5+

    Opera 12+

    Safari 4+

    Internet Explorer 8+

    Chrome,Firefox,Opera,Safari都使用XMLHttpRequest2对象,IE使用类似的XDomainRequest对象,与他本身对应的XMLHttpRequest对象工作机制基本相同,但是另外增加了安全防范。

    首先要创建合适的请求对象,像这样:

事件处理

    原始的XMLHttpRequest只有一个事件处理onreadystatechange,来处理所有的响应。虽然onreadystatechange依然可以使用,XMLHttpRequest2引进了一系列新的处理,下面是一个列表:

    在大多数情况下,你只仅仅想处理onload和onerror事件:

    当有错误的时候,浏览器不会报告是什么错误。例如,FF对所有错误报告一个状态0和空statusText。浏览器也会向控制输出台报错,但是这个消息没法被javascript获取到。当处理onerror的时候,会知道有一个错误发生了,但没有更多信息了。

withCredentials

    标准的CORS在默认情况下不发送和设置任何cookie。为了使cookie成为请求的一部分包含进来,需要设置XMLHttpRequest的withCredentials属性为true:

    为了达成这个目的,服务器也需要设置Access-Control-Allow-Credientials为true来允许认证。

    withCredentials属性会包含来自远程域的请求的任何cookie。注意这些cookies仍然遵循同源策略,所以你的javascript代码仍然从document.cookie中访问不到这些cookies或者请求头.他们只能被远程域所控制。

发送请求

    现在你的CORS请求已经配置好了,你准备发送请求。这个通过调用send()方法来实现:

    如果请求包含请求体,可以被指定为一个参数给send().

    就是酱紫!假定服务器已经完全配置好来响应CORS请求了,你的onload处理会收到响应,就像你十分熟悉的标准的同域XHR那样。

最后最后一个例子

    这是一个完整的CORS请求例子。运行下例子在浏览器debugger里面看看发送的请求。

在服务器端添加CORS支持

    多数的CORS重担在浏览器和服务器之间被处理了。浏览器增加了额外的一些头,有时发起额外的请求,在CORS请求期间。这些额外的请求对于客户端是不可见的(可以使用Wireshark等工具来捕获)。

 

    浏览器厂商对于浏览器端的实现是有责任的。下面了解下服务器端如何配置来支持CORS。

请求的类型

跨域请求分类:

    1.简单请求

    2.非简单请求

    简单请求不需要使用CORS从浏览器发出请求。例如,一个JSONP跨域请求可以是一个跨域的GET请求。

    非简单请求,在浏览器与服务器间需要一点额外的交互。

处理一个简单请求

    以一个客户端的简单请求为例。下面代码展示了javascript的一个GET请求,以及浏览器发送的实际请求。CORS中特殊的头粗体显示。

    首先需要注意的是,一个合法的CORS请求总是包含Origin头。这个Origin头是浏览器加上去的,并且不能被用户控制。这个头的值是来自请求源的协议(例如http),域(例如bob.com),和端口(只在不是默认端口的情况下包含,例如81);例如http://api.alice.com.

    Origin头的存在不意味着请求是一个跨域请求。虽然所有跨域的请求都会包含Origin头,一些同域请求同样也会包含Origin头。例如,火狐在同域请求中不包含Origin头。但是Chrome和Safari在同域的POST/PUT/DELETE请求(同域的GET请求不会有Origin头)中会包含Origin头。下面有一个同域请求包含Origin头的例子:

    值得庆幸的是浏览器在同域请求时不会期望CORS请求头。同域请求的响应发送给用户,不管是否有CORS请求头。然而,如果服务器代码返回了如果Origin不匹配允许的域列表的错误,确保包含请求的来源域。

    所有CORS相关的的头都是Access-Control为前缀的。下面是每个头的一些细节。

    Access-Control-Allow-Origin(必须)-这个头在所有合法的CORS响应中必须被包含;发送头会引起CORS请求失败。这个头的值可以是Origin请求头的回应(就像上面的例子里面一样),或者是允许来自任何域请求的"*"。如果你希望任何站点都可以获取数据,使用"*"就很好。但是如果你希望更好的控制谁可以获取你的数据,在头中使用一个实际的值。

    Access-Control-Allow-Credentials(可选)-默认情况下,cookies在CORS请求中不被包含。使用这个头意味着在CORS请求中应当包含cookies.这个头的唯一合法值是true(全部小写)。如果不需要cookies,不要包含这个头(而不是把值设置为false).

    Access-Control-Allow-Credentials头与XMLHttpRequest2的withCredentials属性结合使用。所有这些属性都必须设置为true以便于CORS请求能够成功。如果withCredentials是true,但是没有Access-Control-Allow-Credentials头,请求会失败(反之亦然)。

    上面提到过,除非你确定需要cookies包含在请求中,否则不设置这个头。

    Access-Control-Expose-Headers(可选)-XMLHttpRequest2对象有一个返回特别响应头值的getResponseHeader()方法。在CORS请求中,getResponseHeader()只能获取简单的响应头。简单响应头定义如下:

    如果希望客户端获取其他头,必须用Access-Control-Expose-Headers头。这个头的值是一个逗号分隔的想要公开给客户端的响应头列表。

处理非简单请求

    上面就是如何处理简单GET请求,但如果想要做更多的事情要怎么做?也许你想要支持其他HTTP动作,像PUT或DELETE,或者想要使用Content-Type:application/json支持JSON.你就需要处理所谓非简单请求。

    非简单请求看上去像到客户端的一个单一请求,但是实际上在遮罩下包含了两个请求。浏览器首先发出一个预先请求,就好像询问服务器应允进行实际请求。一旦被允许,浏览器发送实际请求。浏览器透明的处理这两个请求的细节。预响应也可以被缓存下来以便不需要在每个请求前都发送。

    下面是一个非简单请求的例子:

    像简单请求一样,浏览器给每个请求加上Origin头,包含预请求。预请求像HTTP OPTIONS请求一样发出(所以确保服务器可以响应这个方法).他也包含额外的一些头:

     Access-Control-Request-Method-HTTP实际请求的方法。这个请求头总是被包含,即使HTTP请求是一个简单请求,就像早些定义的那些(GET,POST,HEAD).

     Access-Control-Request-Headers-一个在请求中包含的非简单头的逗号分隔的列表。

    预请求是在发送实际请求前,问询服务器是否允许实际请求的方式。服务器应当检查上面的两个头来确认HTTP方法和请求头是合法的及可以接受的。

    如果HTTP请求方法和头是合法的,服务器会如下响应:

    Access-Control-Allow-Origin(必须)-如简单响应一样,预响应必须包含这个头。

    Access-Control-Allow-Method(必须)-所支持的HTTP方法逗号分隔的列表。注意虽然预请求只是询问HTTP方法是否允许,响应头可以包含所有支持的HTTP方法。这个是有帮助的,因为预响应可能会被缓存,所以单一的预响应可以包含多种请求类型的细节。

    Access-Control-Allow-Headers(如果请求头包含Access-Control-Allow-Headers就必须包含)-逗号分隔的所支持的请求头列表。像上面的Access-Control-Allow-Methods一样,这个可以列出服务器支持的所有头(不仅仅是在预请求中请求的头)。

    Access-Control-Allow-Credentials(可选)-同简单请求。

    Access-Control-Max-Age(可选)-在每个请求上进行预请求是代价很高的,因为浏览器对于客户端的请求会进行两次请求。这个头的值允许预响应缓存一个指定的秒数。

    一旦预请求给到了应允,浏览器发送实际请求。实际请求看起来像简单请求,响应以同样的方式处理:

    如果服务器想否决CORS请求,可以只返回一个通用的响应(像HTTP 200),不带任何CORS请求头。如果预请求时的HTTP方法或者头不合法,服务器可能想要否决请求。由于在响应中没有CORS特定的头,浏览器断定请求是不合法的,不发送实际请求:

    如果在CORS请求中有错误,浏览器会触发客户端的onerror事件处理。也会在控制台上打印下面的错误:

    浏览器不会给为什么错误会发生的细节信息,只告诉你出了些问题。

关于安全

    虽然CORS奠定了跨域请求的基础工作,CORS头不是健全的安全实践的替代。在站点上不能够依赖CORS头保护资源安全。使用CORS头给予浏览器跨域请求的接入,但是使用其他的安全手段,像cookies或者OAuth2,如果需要额外的安全限制的话。

来自jQuery的CORS

    jQuery的$.ajax()方法可以用来进行常规XHR请求和CORS请求。一点点jQuery相关实现的笔记:

  •  jQuery的CORS实现不包含IE的XDomainRequest对象。但是有jQuery插件可以用这个。可以在http://bugs.jquery.com/ticket/8283上查看细节。
  •  如果浏览器支持CORS的话(在IE中会返回false,看下上条说明),$.support.cors布尔量会被设置成true。这是检查是否支持CORS的快速方法。

    下面是一个用jQuery进行CORS请求的例子。注释给出了确切的属性如何与CORS交互。

Chrome扩展件的跨域

    Chrome扩展件以两种不同的方式支持跨域:

  1. 在manifest.json中包含域-Chrome扩展件可以向任何域发送跨域请求,如果域被包含在manifest.json文件的permissions部分:服务器不需要包含任何额外的CORS头或者做另外的工作来使请求成功。
  2. CORS请求-如果域不在manifest.json文件中,Chrome扩展件进行一个标准的CORS请求。Origin头的值是"chrome-extension://[CHROME EXTENSION ID]".这意味着来自Chrome扩展件的请求受本文提到的同样的CORS请求规则的约束。

CORS w/图片

    在Canvas和WebGL上下文中,跨域图片可以造成很大的问题。可以在img元素上使用crossOrigin属性。

CORS服务器流程图

    下面的流程图说明了服务器如何确定在CORS响应中需要加哪些头。

参考地址:https://www.w3.org/TR/2008/WD-access-control-20080912/

展开阅读全文

没有更多推荐了,返回首页