YIXUNFE / blog

文章区
151 stars 25 forks source link

关于 CORS —— 小船翻在了 IE8 的小沟沟里 #50

Open YIXUNFE opened 8 years ago

YIXUNFE commented 8 years ago

关于 CORS —— 小船翻在了 IE8 的小沟沟里

如今使用 CORS 技术做跨域通信已经不是什么新鲜事情了,我们网页中的很多接口在跨域方案上也首选 CORS。然而 CORS 看似完美的背后,却有值得我们注意的地方。

我们团队在开发新需求的时候,测试 MM 发现 IE8 下网页中的一个 CORS 接口返回异常,提示没有权限,排查这个问题的过程中加深了我们对 IE 实现 CORS 技术的认知。


CORS 是什么

网上已经有太多的篇幅描述 CORS 是什么,这里简单介绍一下。CORS 技术就是为了实现跨域请求,而在响应头中设置一些 CORS 技术规定的响应头,如 Access-Control-Allow-Origin 等用以实现客户端与服务端进行跨域通信。

通常一个简单的 CORS 响应头组成有如下内容:

Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: http://www.yixun.com

CORS 方案的 HTTP 请求是不会携带 cookie 的,如果需要带上 cookie,需要在响应头中多添加一些字段:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:withCredentials

同时客户端在请求头中需要设置字段 withCredentialstrue

withCredentials: true

*允许带 cookie 时,Access-Control-Allow-Origin 字段值不可以设置为 ``。**


为什么选择 CORS 方案

CORS 方案较之于传统的方案优点很多。

总结下来,选择 CORS 跨域方案最大的好处是返回数据可以在跨域与同域时完美兼容,不需要接口多 处理不同情况下的返回数据格式,前端实现也十分简单方便。


IE8 下为什么请求提示没有权限?

返回文章起首提到的问题,请求接口提示没有权限。但是在 Chrome、Firefox 中确认接口是正确添加了 CORS 需要的响应头字段了。

后来在爆栈上搜索发现,原来 IE8\9 实现 CORS 的对象和实现 AJAX 的对象(XMLHTTP)不是同一个,而是使用了 window.XDomainRequest 对象

由于我们的 PC 站使用的 jQuery 版本太低(5.1版),$.ajax 方法没有兼容 IE8\9 的 XDomainRequest 对象,于是网页使用了 XMLHTTP 对接口发起请求,所以会返回“没有权限”的提示。为了兼容这个情况,我们做了一些手动的兼容:

if (/*判断是否跨域*/ && window.XDomainRequest) {
  var xdr = new XDomainRequest()
  xdr.open('post', url)
  xdr.onload = function() {
    //success(xdr.responseText);
  }
  xdr.send()
} else {
  $.ajax(...)
}

在 IE8/9 下使用 XDomainRequest 进行跨域时还有一些限制:

在有上述两项需求并需要兼容 IE8\9 时,CORS 方案就无法满足我们的需求了,需要定制另外的跨域方案。于是我们有部分接口只能通过修改接口将需要的 cookie 值放在请求的 query string 中或者采用其他的跨域方案解决问题。


扩展阅读

document.domain 跨域方案

当仅二级域名不同时,通过两个页面设置相同的主域名,比如

//base.yixun.com
document.domain = 'yixun.com'

就可以互相通信。接口可以通过 form 元素的 target 属性指定提交至一个 iframe 元素中,接口中返回带有格式的数据,如:

<!--接口的返回数据-->
<script>
document.domain = 'yixun.com'
frameElement.callback(...) // frameElement 即 iframe 元素本身,callback 方法在父页面中定义
</script>

iframe 元素得到了一个 script 标签并执行,而 iframe 元素的 callback 预先已经定义好:

ifr = document.getElementById(...) //获取 iframe 元素
ifr.callback = function (d) { //定义 iframe 元素的 callback 方法
  success(d)
  this.src = 'about:blank' //避免页面刷新重复提交
}

postMessage 方案和 document.domain 原理相似,就不再赘述。


Thanks