CORS 简聊
lanbin opened this issue · comments
CORS
CORS(Cross-Origin Resource Sharing)
跨来源资源共享是一份浏览器技术的规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。
与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。
用 CORS 可以让网页设计师用一般的 XMLHttpRequest,这种方式的错误处理比 JSONP 要来的好。另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作。现代的浏览器都支持 CORS。
跨域方法回顾
前端页面跨域的需求一直都存在。因为一个设计良好的系统,并不会把所有的服务都统统部署在一起。系统进行划分之后,就会出现页面和资源存在不同域名的情况。
而跨域判断其实是一种客户端行为,即在不同的客户端中是否跨域的判断并不一样。
我们此处只讨论在浏览器中的情况
在古代,为了防止出现跨域的问题,当然也有一些简单的办法
比如,主站域名为 http://www.example.com,
那么资源域名就设定一个路由,http://www.example.com/api/
这种情况适用于单一产品线,单一服务。
如果是多产品线, 单一服务/多服务,则不太适用了。
JS跨域分析
要解决问题,首先要知道问题是什么。
那么什么情况就是跨域呢
例如, 域名 http://www.example.com
URL | 结果 | 原因 |
---|---|---|
http://www.example.com/api/info.json | 不跨域 | 同源 |
https://www.example.com/api/info.json | 跨域 | 协议不一样 |
http://www.example.com:81/api/info.json | 跨域 | 端口不一样 |
http://api.example.com/api/info.json | 跨域 | 子域名不一样 |
解决跨域
问题知道了,那我们看看怎么来解决:
- JSONP
- document.domain
- window.name
- window.postMessage
以上4种为常见的方法。
之所以提前列出来,是因为他们的基本原理都相似, 即:
后端的资源接口根据具体的情况,把返回的数据生成一段html或者javascript。页面将此资源文件加载进来并执行
1. JSONP
因为<script>标签不受同源策略的限制,所以JSONP的方式相当于是我们从服务端请求了一个Javascript文件,只是这个文件的内容是后端需要返回的数据,并且包裹上了一个回调方法。
例如: http://www.example.com/api/info.json?callback=callbackFun
callbackFun({
data: ...
})
此callback方法的方法名已经在请求发出前注册到了window上。
当这个地址被加载到页面上即会立刻运行,调用回调函数。
比如jQuery中,callback名字就是一个随机的函数名: jQueryxxxxxx_xxxxxxx. 这样能够保证每次发出的JSONP请求都是独立的,返回之后的处理也是互相不干扰的。(这种模式也被用在早期的app和Hybrid的交互中)
目前topicpage就是用的类似的方法,但是因为数据是在cdn上的,所以回调方法的名称是写死的。
在页面上注册好方法之后,调用不同的数据文件即可拿到cdn的数据。
2. document.domain
这个方法的要求会比较多
- 有其他页面
window
对象的引用。 - 二级域名相同。
- 协议相同。
- 端口相同。
即页面和请求的资源都要是在一个一级域名下。
这种方式,一般会在主页面上设置一个iframe,来请求资源文件。
然后在主页面写Javascript:
var iframe = document.getElementById('iframe');
document.domain = 'example.com';
iframe.contentDocument; // 框架的 document 对象
iframe.contentWindow; // 框架的 window 对象
此方法存在跨域攻击的风险,现在用得非常少了。
剩下的方法参考: 「JavaScript」四种跨域方式详解
CORS跨域解决方案
说回CORS。
CORS的机制和上面的存在着很大的不同。
因为它使用的是服务端+客户端双方验证机制。
所以客户端的是否支持也很重要。
大概描述一下CORS主要都做了什么。
首先: 客户端在请求头中表示,
【我来自哪里】 — origin
【我是否携带了证书】 — withCredentials
【我要做什么】— method
然后:服务端接受到请求之后进行处理,请求的返回头中表示,
【我们允许的请求只能来自哪里】— access-control-allow-origin
【我们是否允许携带证书】 — access-control-allow-credentials
【我们允许的方法有】— access-control-allow-method
因为,请求的返回头(也就是在chrome里面看到的请求的Response Headers的部分)是由服务端进行返回的,所以此处前端是无法修改的。
这时,客户端(浏览器)只要比对请求头和返回头的内容是否相符合即可判断此次请求是否合法(跨域)。
不带验证的普通情况
一般来说,普通的GET、POST、HEAD的请求不会发送“预请求“OPTIONS。
当我们发出这样的请求时,只需要服务端返回的 access-control-allow-origin 设置成 ***** 或者来源的域名就可以完成跨域
配置 | 值 |
---|---|
access-control-allow-credentials | null / false |
access-control-allow-origin | * / http://www.example.com |
***** 通配符表示,允许所有域名来请求当前资源
此时,默认的 access-control-allow-credentials 为空或者false
带验证的特殊情况
但是,大部分时候我们需要在发送请求的时候带上cookie以做身份校验。
这个时候就需要在发送请求时,设置 credentials 为 true
同时,在服务端需要对返回头设置:
配置 | 值 |
---|---|
access-control-allow-credentials | true |
access-control-allow-origin | http://www.example.com |
这种情况下,credentials必须为true,origin必须有值不能为通配符
其实,情况很简单,就两种。
只是 credentials 和 origin 的情况必须匹配起来。
最近我们遇到的情况是这样的:
后端增加了接口307跳转到cdn,
但是cdn只能设置origin为 * 或者null,
但是设置为null的情况下ios8/9又会出现跨域的bug。
本来我们所有的接口都是设置的credentials为true都携带cookie,但是为了能达到cdn,所以相关的接口就只能设置为false,方便cdn返回避免跨域。
整体流程最后梳理为:
前端 | ysgge | 后端 | cdn |
---|---|---|---|
credentials: false | 透传 | allow-credentials:false / allow-origin : * | allow-credentials:false / allow-origin : * |
credentials: true | 透传 | allow-credentials:true / allow-origin : http://www.example.com | 无跳转 |
但是vue-resouce的1.x版本和0.x版本对于不同接口的credentials设置不一样,要引起注意。
参考: