wengjq / Blog

个人博客
581 stars 47 forks source link

前端跨域问题及解决方案 #2

Open wengjq opened 7 years ago

wengjq commented 7 years ago

1、同源策略

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。

一个源的定义:如果协议,端口(如果指定了一个)和主机对于两个页面是相同的,则两个页面具有相同的源。

下表给出了相对 http://store.company.com/dir/page.html 同源检测的示例:

URL 结果 原因
http://store.company.com/dir/inner/another.html 成功 同一域名
http://store.company.com/dir2/other.html 成功 同一域名下不同文件夹
https://store.company.com/secure.html 失败 不同的协议 ( https )
http://store.company.com:81/dir/etc.html 失败 不同的端口 ( 81 )
http://news.company.com/dir/other.html 失败 不同的主机 ( news )

2、主域相同的跨域

document.domain的场景只适用于不同子域的框架间的交互,及主域必须相同的不同源。

页面可能会更改其自己的来源,但有一些限制。脚本可以将 document.domain的值设置为其当前域或其当前域的超级域。如果将其设置为其当前域的超级域,则较短的域将用于后续原始检查。例如,假设文档中的一个脚本在 http://store.company.com/dir/other.html 执行以下语句:

 document.domain = "company.com";

这条语句执行之后,页面将会成功地通过对 http://company.com/dir/page.html 的同源检测。

注:浏览器单独保存端口号。任何的赋值操作,包括document.domain = document.domain都会以null值覆盖掉原来的端口号。因此,company.com:8080页面的脚本不能仅通过设置document.domain = "company.com"就能与company.com通信。赋值时必须带上端口号,以确保端口号不会为null。

(1) 在www.a.com/a.html中:
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src =  'http://www.script.a.com/b.html';  
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){ 
    var doc = ifr.contentDocument || ifr.contentWindow.document;                                                          
    ifr.onload = null;
};

(2) 在www.script.a.com/b.html中:
document.domain = 'a.com';//注意:使用document.domain允许子域安全访问其父域时,您需要设置document域在父域和子域中具有相同的值。这是必要的,即使这样做只是将父域设置回其原始值。否则可能会导致权限错误。这里都是a.com。

3、完全不同源的跨域(两个页面之间的通信)

3.1、通过location.hash跨域

假设域名a.com下的文件cs1.html要和jianshu.com域名下的cs2.html传递信息。 1、cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向jianshu.com域名下的cs2.html页面。 2、cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据。 3、同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值。

注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe。

优点:1.可以解决域名完全不同的跨域。2.可以实现双向通讯。 缺点:location.hash会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。另外由于URL大小的限制,支持传递的数据量也不大。有些浏览器不支持onhashchange事件,需要轮询来获知URL的变化。

3.2、通过window.name跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。

 window.name = data;//父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入。        
 location = 'http://parent.url.com/xxx.html';//接着,子窗口跳回一个与主窗口同域的网址。
 var data = document.getElementById('myFrame').contentWindow.name。//然后,主窗口就可以读取子窗口的window.name了。

如果是与iframe通信的场景就需要把iframe的src设置成当前域的一个页面地址。

3.3、通过window.postMessage跨域

HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。postMessage的兼容性如下:

Paste_Image.png 可以看到 Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都将支持这个功能。但是Internet Explorer 8和9以及Firefox 6.0和更低版本仅支持字符串作为postMessage的消息

var popup = window.open('http://bbb.com', 'title');//父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法。
popup.postMessage('Hello World!', 'http://bbb.com');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

父窗口和子窗口都可以通过message事件,监听对方的消息。message事件的事件对象event,提供以下三个属性:

一个例子:

 var onmessage = function (event) {  
   var data = event.data;//消息  
   var origin = event.origin;//消息来源地址  
   var source = event.source;//源Window对象  
   if(origin == "http://www.aaa.com"){  
    console.log(data);//hello world!  
   }  
    source.postMessage('Nice to see you!', '*');
 };  
 if (typeof window.addEventListener != 'undefined') {  
   window.addEventListener('message', onmessage, false);  
 } else if (typeof window.attachEvent != 'undefined') {  
   //ie  
   window.attachEvent('onmessage', onmessage);  
 }

4、AJAX请求不同源的跨域

4.1、通过JSONP跨域

基本原理:网页通过添加一个 Githubissues.

  • Githubissues is a development platform for aggregating issues.