使用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扩展件以两种不同的方式支持跨域:
- 在manifest.json中包含域-Chrome扩展件可以向任何域发送跨域请求,如果域被包含在manifest.json文件的permissions部分:
服务器不需要包含任何额外的CORS头或者做另外的工作来使请求成功。 - CORS请求-如果域不在manifest.json文件中,Chrome扩展件进行一个标准的CORS请求。Origin头的值是"chrome-extension://[CHROME EXTENSION ID]".这意味着来自Chrome扩展件的请求受本文提到的同样的CORS请求规则的约束。
CORS w/图片
在Canvas和WebGL上下文中,跨域图片可以造成很大的问题。可以在img元素上使用crossOrigin属性。
CORS服务器流程图
下面的流程图说明了服务器如何确定在CORS响应中需要加哪些头。
--end--
相关文章:https://docs.webplatform.org/wiki/tutorials/using_cors
使用CORS:跨域两三事的更多相关文章
- 如何在ASP.NET Core中实现CORS跨域
注:下载本文的完整代码示例请访问 > How to enable CORS(Cross-origin resource sharing) in ASP.NET Core 如何在ASP.NET C ...
- ajax——CORS跨域调用REST API 的常见问题以及前后端的设置
RESTful架构是目前比较流行的一种互联网软件架构,在此架构之下的浏览器前端和手机端能共用后端接口. 但是涉及到js跨域调用接口总是很头疼,下边就跟着chrome的报错信息一起来解决一下. 假设:前 ...
- Web APi之手动实现JSONP或安装配置Cors跨域(七)
前言 照理来说本节也应该讲Web API原理,目前已经探讨完了比较底层的Web API消息处理管道以及Web Host寄宿管道,接下来应该要触及控制器.Action方法,以及过滤器.模型绑定等等,想想 ...
- Web API 实现JSONP或者安装配置Cors跨域
前言 照理来说本节也应该讲Web API原理,目前已经探讨完了比较底层的Web API消息处理管道以及Web Host寄宿管道,接下来应该要触及控制器.Action方法,以及过滤器.模型绑定等等,想想 ...
- CORS 跨域 实现思路及相关解决方案
本篇包括以下内容: CORS 定义 CORS 对比 JSONP CORS,BROWSER支持情况 主要用途 Ajax请求跨域资源的异常 CORS 实现思路 安全说明 CORS 几种解决方案 自定义CO ...
- 解决ajax请求cors跨域问题
”已阻止跨源请求:同源策略禁止读取位于 ***** 的远程资源.(原因:CORS 头缺少 'Access-Control-Allow-Origin').“ ”已阻止跨源请求:同源策略禁止读取位于 ** ...
- Asp.Net WebApi 启用CORS跨域访问指定多个域名
1.后台action指定 EnableCors指定可访问的域名多个,使用逗号隔开 //支持客户端凭据提交,指定多个域名,使用逗号隔开 [EnableCors("http://localhos ...
- Cors 跨域Access-Control-Allow-Origin
1.Access-Control-Allow-Origin 指定格式 The Origin header field has the following syntax: origin = " ...
- Asp.Net WebApi+Microsoft.AspNet.WebApi.Core 启用CORS跨域访问
WebApi中启用CORS跨域访问 1.安装 Nugget包Microsoft.AspNet.WebApi.Cors This package contains the components to e ...
- jsonp与cors跨域的一些理解
浏览器的同源策略,即是浏览器之间要隔离不同域的内容,禁止互相操作. 比如,当你打开了多个网站,如果允许多个网站之间互相操作,那么其中一个木马网站就可以通过这种互相操作进行一系列的非法行为,获取你在各个 ...
随机推荐
- Spark RDD简介与运行机制概述
RDD工作原理: 主要分为三部分:创建RDD对象,DAG调度器创建执行计划,Task调度器分配任务并调度Worker开始运行. SparkContext(RDD相关操作)→通过(提交作业)→(遍历RD ...
- RabbitMQ介绍2 - AMQP协议
这一节介绍RabbitMQ的一些概念,当然也是AMQP协议的概念.官方网站也有详细解释,包括协议的命令: http://www.rabbitmq.com/tutorials/amqp-concepts ...
- ScrollView--嵌套GridView的解决办法
前些日子在开发中用到了需要ScrollView嵌套GridView的情况,由于这两款控件都自带滚动条,当他们碰到一起的时候便会出问题,即GridView会显示不全. 解决办法,自定义一个GridVie ...
- 转-WebView loadData与loadDataWithBaseURL用法、区别
近期用到WebView加载一些动态的内容的时候遇到一些问题,例如:在加载动态网页时,页面有很多样式表包含一些特殊字符,导致WebView无法识别产生加载异常,程序直接崩溃:另外一个就是加载的网页中有图 ...
- 版本控制、SVN、VSS
ylbtech-Miscellaneos: 版本控制.SVN.VSS 1.A,版本控制返回顶部 1, 版本控制(Revision control)是一种软体工程技巧,籍以在开发的过程中,确保由不同人所 ...
- PHP批量替换MySql数据库中的数据内容(替换MySql数据库内容源码)
PHP批量替换MySql数据库内容 UTF-8 1.0版 <?php //声明 //1.本源码开发意图:作者在使用一些CMS建站的时候发现很多CMS把网址写入到数据库了,如果换网址,那么就需要更 ...
- NSSet类型 以及与NSArray区别
NSSet到底什么类型,其实它和NSArray功能性质一样,用于存储对象,属于集合: NSSet , NSMutableSet类声明编程接口对象,无序的集合,在内存中存储方式是不连续的,不像NSAr ...
- storyBoard中切换应用启动的切入点方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...
- Mac 上SVN上传.a文件
SVN默认是忽略.a文件,所以修改配置文件去掉忽略配置行的 *.a 通过终端打开配置文件: open ~/.subversion/config 把下面两行(也可能是一行)中的注释和*.a去掉, #gl ...
- IntelliJ IDEA 注册码
IntelliJ IDEA 注册码 *.lanyus.com及*.qinxi1992.cn下的全部授权服务器已遭JetBrains封杀 请搭建自己的IntelliJ IDEA授权服务器,教程在http ...