Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器跨域解决方案 #8

Open
xuexueq opened this issue Sep 17, 2018 · 0 comments
Open

浏览器跨域解决方案 #8

xuexueq opened this issue Sep 17, 2018 · 0 comments

Comments

@xuexueq
Copy link
Owner

xuexueq commented Sep 17, 2018

什么是跨域

浏览器的同源策略会导致跨域,这里同源策略又分为以下两种DOM同源策略:

  • XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
  • 禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。

即不能通过ajax的方法去请求不同源中的文档、浏览器中不同域的框架之间是不能进行js的交互操作的。

只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。

还有一点比较重要,为了安全,限制跨域是浏览器的行为,而不是JS/服务端的行为。你也可以自己开发一个浏览器,或者拿开源代码改,使得自己开发的浏览器能够跨域。

  1. 哪些情况属于跨域:
  • 协议不同,如http, https;
  • 端口不同;
  • 主域相同,子域不同;(域名不同)
  • 主域不同;(域名不同)
  • ip地址和域名之间也算是跨域,浏览器不会自动做ip域名的映射;
  1. 限制范围
  • Cookie、LocalStorage 和 IndexDB 无法读取。
  • DOM 无法获得。
  • AJAX 请求不能发送。
  1. 解决方案
  • jsonp
  • cors
  • WebSocket
  • 服务器代理转发
  • postMessage

AJAX请求不同源的跨域

一、 JSONP(浏览器与服务端的通信)

为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP。该协议的一个要点就是通过创建一个 script 标签,将 src 设置为目标请求,插入到 dom 中,服务器接受该请求并返回数据,数据通常被包裹在回调钩子中

直接打开 index.html 文件浏览器端会报错。就是同源限制造成的。

// index.html
<!DOCTYPE html>
   <html lang="en">
   <head>
       <meta charset="UTF-8">
       <title>Document</title>
   </head>
   <body>
       
   </body>

    <script>
        function request(type, url, data) {
            const xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
                        console.log(xhr.responseText)
                    } else {
                        console.log(xhr.status)
                    }
                }
            }
            xhr.open(type, url, true)
            xhr.send(data)
        }
        request('get', 'http://localhost:3000/user', null) // 网页默认端口 80 or 8000
    </script>
   </html> 

1. JSONP 的实现原理(利用script标签不受跨域限制而形成的一种方案)

JSONP 是 JSON with padding (填充式JSON或参数式JSON)的简写,是应用JSON的一种方式。

JSONP的实现原理很简单,利用 <script> 标签没有同源限制的特点,也就是 <script> 的src链接可以访问不同源的。不受同源限制的还有 <img><iframe><link>,对应这两个标签实现的跨域方法也有,比如图片ping等。(图片ping:可以访问任何url,一般用来进行点击追踪,做页面分析常用的方法。缺点:不能访问响应文本,只能监听是否响应)

下面为允许跨域资源嵌入的示例,即一些不受同源策略影响的标签示例:

  • <script src="..."></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
  • <link rel="stylesheet" href="...">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari 和 Opera。
  • <img>嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG
  • <video><audio>嵌入多媒体资源。
  • <object>, <embed><applet>的插件。
  • @font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  • <frame><iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

我们访问服务端,一般是获取存JSON数据,而JSONP则返回的是,包含函数的数据,将我们需要的 JSON 数据作为函数的参数。

即在客户端一般通过 <script> 标签的 src 访问带有 callback 查询参数的请求,来获取返回带有函数的数据,然后执行它,即可拿到这份数据(服务端注入的数据)完成跨域访问。

// 服务器返回的内容: 以字符串的形式返回
callback({"name": "xuthus"};

由于使用 script 标签的 src 属性,因此只支持get方法。

JSONP的请求过程

  • 请求阶段:浏览器创建一个 script 标签,并给其 src 赋值(同时在客户端注册一个 cb 方法 jsonpCallback() ,放到 window 对象上;并把 callback 的名字( jsonpCallback )传给服务器:类似 http://example.com/api/?cb=jsonpCallback ),再将 script 标签插入 DOM。
  • 发送请求:当给 scriptsrc 赋值并插入 DOM 后(不将 script 标签插入 DOM ,是不会发起请求的),浏览器就会发起一个请求。
  • 数据响应:服务端将要返回的数据作为参数和函数名称拼接在一起(字符串的形式,格式类似"jsonpCallback({name: 'abc'})"返回。当浏览器接收到了响应数据,由于发起请求的是 script,所以相当于直接调用 jsonpCallback 方法,并且传入了一个参数。(解析 script 标签后,会执行 jsonpCallback(JOSN) )。

通过这种方式,即可实现跨域获取数据。

2. 封装一个jsonp工具

function createScript(url) {
    let script = document.createElement('script');
    script.setAttribute('src', url);
    script.setAttribute('type', 'text/javascript');
    script.async = true; // 或者直接插入文档底部 document.body.appendChild(script);
    return script;
}
// NumberObject.toString(radix) radix数字转化的基数:2~36之间的整数,36表示36进制,能将26个字母全部运用上
// /[^a-z]+/g :[^a-z]表示除a~z之间的字符 +:{1,}表示出现的所有字符
// 字符串的方法slice和substring都表示切割从起始位置到结束位置(不包括)之间的字符 substr()第一个参数表示切割的起始位置 第二个参数表示切割的长度
function gernerateCbName(prefix, num) {
    return prefix + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, num);
}

// 我们将所有的callback都设置成了一个全局变量,这样的原因是因为我们需要在数据请求完成之后调用这个方法,因此不得不设置为一个全局变量。
// 但是当我们有多个请求,并且每个请求的处理都是不一样的时候,这个变量将会被覆盖。这是不行的,因此我们应该为每一次请求设置一个唯一且不会冲突的变量
// 页面中若有大量这样的请求之后,window中会含有大量的全局变量,而且还有大量的script标签,这显然不是我们想要的,所以我们要在请求完成之后删除变量和script标签。
function jsonp({ url, params, timerout = 0}) {
    let timer = null;
    let cbName = gernerateCbName('cb');
    let arr = [];
    params = { ...params, cbName }
    for(let key in params) {
        arr.push(`${key}=${params[key]}`)
    }

    let script = createScript(`${url}?${arr.join('&')}`);
    document.getElementsByTagName('head')[0].appendChild(script);

    // 错误处理(例如资源加载失败)
    script.onerror = function() {
        reject(new Error(`fetch ${url} failed`));
        window[cbName] = null;
        timer && clearTimeout(timer);
        document.getElementsByTagName('head')[0].removeChild(script);
    }

    return new Promise((resolve, reject) => {
        window[cbName] = function(data) {
            resolve(data);
            window[cbName] = null;
            timer && clearTimeout(timer);
            document.getElementsByTagName('head')[0].removeChild(script);
        }

        // 超时处理
        if(timerout != 0) {
            timer = setTimeout(() => {
                reject(new Error('TimeOut'));
                timer && clearTimeout(timer);
                // window[cbName] = null;
                // document.getElementsByTagName('head')[0].removeChild(script);
            }, timerout);
        }
    });
}

// 使用
jsonp({
    url: 'http://localhost:3000',
    params: {
        name: 'xql'
    },
    timerout: 1
}).then((data) => {
    console.log(data);
}).catch((err) => {
    console.log(err);
});

测试:node server.js 打开index.js 可以看到跨域成功

// server.js
let express = require('express');
let app = express();
app.get('/', function(req, res){
    let { cbName } = req.query; // cbName是和前端约定好的字段名称,即服务端和客户端这个字段保持一致
    console.log(cbName);
    res.end(`${cbName}('hello')`); // `${cbName}(数据 or JSON数据)`
});
app.listen(3000);

3. 注意

  • JSONP虽然可以兼容老版的浏览器,只支持GET请求而不支持POST等其它类型的HTTP请求;
  • 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
  • 容易受xss攻击(安全问题)。在使用JSONP的时候必须要保证使用的JSONP服务必须是安全可信的。万一提供JSONP的服务存在页面注入漏洞,它返回的javascript都将被执行,若被注入这是非常危险的。
  • JSONP在调用失败的时候不会返回各种HTTP状态码(解决方法:添加timeout参数,虽然JSONP请求本身的错误没有被捕获,但是最终会因为超时而执行error回调)

二、CORS(通过前后端http header配置来进行跨域的一种方式)

Cross-Origin Resource Sharing 跨源资源共享

浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

可以把CORS分为:简单请求、复杂请求。

1. 简单请求

  • 只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
  • 不会使用自定义请求头(类似于 X-Modified 这种)。

流程:

  • 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,自动增加一个Origin字段:表明本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
  • 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
    如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com: 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
Access-Control-Allow-Credentials: true:该字段可选。它的值是一个布尔值且只有 true 这一个值,表示是否允许发送Cookie,同时开发者必须在AJAX请求中打开withCredentials: true属性才生效。默认情况下,Cookie不包括在CORS请求之中。

2. 非简单请求

2.1 预检请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。
Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,例如PUT。Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,例如X-Custom-Header。

2.2 预检请求的回应

服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

  • Access-Control-Allow-Origin字段,表示该域名可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。

  • Access-Control-Allow-Methods: 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

  • Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

  • Access-Control-Allow-Credentials:该字段与简单请求时的含义相同。

  • Access-Control-Max-Age:该字段可选,用来指定本次预检请求的有效期,单位为秒。例如:有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

2.3 浏览器的正常请求和回应

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

优点

  • 前端方便不少,只需要发请求而不用考虑跨域问题;
  • 安全性能够得以控制和保障;

缺点

  • 兼容性不全面,需要做降级处理;

使用方式

  • 正常请求即可,无论是你要用xhr,还是用一些封装好的组件,如fetch,fetchJsonp,亦或是jquery一类的技术均可;
  • 后端在response时需要设置一定的配置参数,并保证安全策略

CORS和JSONP对比

  • JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
  • 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
  • JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。

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

二、 postMessage

如果两个网页不同源,就无法拿到对方的DOM。典型的例子是 iframe 窗口和window.open方法打开的窗口,它们与父窗口无法通信。

比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。

// 父窗口想获取子窗口的DOM,因为跨源导致报错。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.

反之亦然,子窗口获取主窗口的DOM也会报错。

window.parent.document.body
// 报错

如果两个窗口一级域名相同,只是二级域名不同,那么设置document.domain属性,就可以规避同源政策,拿到DOM。

对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。

    1. 利用 location.hash + iframe
    1. 利用 window.name + iframe
    1. 跨文档通信API: window.postMessage
1 通过修改document.domain来跨子域(只适用于主域相同、不同子域的框架间的交互)

有一个页面,它的地址是 http://www.example.com/a.html , 在这个页面里面有一个 iframe,它的src是 http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的。

  • 在页面 http://www.example.com/a.html 中设置 document.domain:
<iframe id = "iframe" src="http://example.com/b.html" onload="test()"></iframe>
<script type="text/javascript">
    // function test(){
    //     var iframe = document.getElementById('ifame');
    //     var win = document.contentWindow;// undefined 可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
    //     var doc = win.document;//这里获取不到iframe里的document对象
    //     var name = win.name;//这里同样获取不到window对象的name属性
    //     console.log(win)
    // }
    document.domain = 'example.com';//设置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
    }
</script>
  • 在页面 http://example.com/b.html 中也设置document.domain:
<script type="text/javascript">
    document.domain = 'example.com';
    //在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>
2 利用 location.hash + iframe(这种跨域方法主要是通过设置/监听url的hash部分,来实现跨域)

原理:location.hash方式跨域,是子框架能够修改父框架src的hash值,子框架通过修改父框架 src 的 hash 值来传递信息。且更改hash值,页面不会刷新。但是传递的数据的字节数是有限的。

步骤:动态插入一个iframe,将iframe的src属性指向服务端地址。这时top window和包裹这个iframe的子窗口是不能通信的(同源策略),所以改变子窗口的路径就行了,将数据当做改变后的路径的hash值加在路径上,然后就能通信了(和window.name跨域几乎相同),将数据加在index页面地址的hash值上。index页面监听地址的hash值变化(html5有hashchange事件,用setInterval不断轮询判断兼容ie6/7),然后做出判断,处理数据。

假设域名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。

//a页面的代码
<script type="text/javascript">
   iframe = document.createElement('iframe');
   iframe.style.display = 'none';
   var state = 0;

   iframe.onload = function() {
     if(state === 1) {
         var data = window.location.hash;
         console.log(data);
         iframe.contentWindow.document.write('');
         iframe.contentWindow.close();
         document.body.removeChild(iframe);
     } else if(state === 0) {
         state = 1;
         iframe.contentWindow.location = 'http://localhost: 4000/b.html';
     }
   };
   document.body.appendChild(iframe);
</script>
//b页面代码
<script type="text/javascript">
   parent.location.hash = "world";
</script>

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

3 利用 window.name + iframe (父框架和子框架的src必须指向同一域名)

原理:window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,每个 iframe 都有 window.name 的属性。window.name属性的神奇之处在于一个 iframe 的 name 值在不同的页面(甚至不同域名,即改变了这个iframe的 src 默认情况下,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的指向,使之与index.html同源,那么index页面就能去获取到这个 iframe 的name值了(把子框架的 src 改变和父框架同源,而子框架的 name 依然保持不变)!

所以,iframe子框架需要一直不停地刷新,每次触发onload事件后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的);

// index.html
  <script type="text/javascript"> 
    iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = 'http://localhost: 4000/b.html';
    document.body.appendChild(iframe);
    var state = 0;
    
    iframe.onload = function() {
      if(state === 1) {
          var data = JSON.parse(iframe.contentWindow.name);
          console.log(data);
          iframe.contentWindow.document.write('');
          iframe.contentWindow.close();
        document.body.removeChild(iframe);
      } else if(state === 0) {
          state = 1;
          iframe.contentWindow.location = 'http://localhost: 8000/proxy.html'; // 父框架的域
      }
    }; 
  </script>
// http://localhost: 4000/b.html文件
<script type="text/javascript">
   window.name = "hello";
</script>

优点:window.name容量很大,可以放置非常长的字符串;(2MB)
缺点:必须监听子窗口window.name属性的变化,影响网页性能。

4 window.postMessage

信息传递除了客户端与服务器之前的传递,还存在以下几个问题:

  • 页面和新开的窗口的数据交互。
  • 多窗口之间的数据交互。
  • 页面与所嵌套的iframe之间的信息传递。

上面两种方法都属于破解,再加上iframe用的比较少了,所以这些方法也就有点过时了。window.postMessage是一个HTML5的api,允许两个窗口之间进行跨域发送消息,不论这两个窗口是否同源。这个应该就是以后解决dom跨域通用方法了。

// 语法:data:将要发送到其他 window 的数据; targetOrigin:指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI
otherWindow.postMessage(data, targetOrigin, [transfer]);

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

  • event.source:发送消息的窗口。
  • event.origin: 消息发向的网址。
  • event.data:消息内容。
// a.html
<iframe src="http://localhost:4000/b.html" id="frame" onload="load()"></iframe>
<script>
function load(params){
    let frame = document.getElementById('frame');
    //获取iframe中的窗口,给iframe里嵌入的window发消息
    frame.contentWindow.postMessage('hello','http://localhost:4000')
    // 接收b.html回过来的消息
    window.onmessage = function(e){
        console.log(e.data)
    }
}
</script>
// b.html
<script>
//监听a.html发来的消息
window.onmessage = function(e){
    console.log(e.data)
    //给发送源回消息
    e.source.postMessage('nice to meet you',e.origin)
}
</script>
/*
 * A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:
 */

var popup = window.open(...popup details...);

// 如果弹出框没有被阻止且加载完成

// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
  // 我们能相信信息的发送者吗?  (也许这个发送者和我们最初打开的不是同一个页面).
  if (event.origin !== "http://example.org")
    return;

  // event.source 是我们通过window.open打开的弹出页面 popup
  // event.data 是 popup发送给当前页面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
 * 弹出页 popup 域名是<http://example.org>,以下是script标签中的代码:
 */

//当A页面postMessage被调用后,这个function被addEventListenner调用
function receiveMessage(event)
{
  // 我们能信任信息来源吗?
  if (event.origin !== "http://example.com:8080")
    return;

  // event.source 就当前弹出页的来源页面
  // event.data 是 "hello there!"

  // 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把enent.source
  // 作为回信的对象,并且把event.origin作为targetOrigin
  event.source.postMessage("hi there yourself!  the secret response " +
                           "is: rheeeeet!",
                           event.origin);
}

window.addEventListener("message", receiveMessage, false);

优点:不需要后端介入就可以非常简单的的做到跨域,一个函数外加两个参数(请求url,发送数据)就可以搞定;移动端兼容性好;
缺点

  • 无法做到一对一的传递方式:监听中需要做很多消息的识别,由于postMessage发出的消息对于同一个页面的不同功能相当于一个广播的过程,该页面的所有onmessage都会收到,所以需要做消息的判断;
  • 安全性问题:三方可以通过截获,注入html或者脚本的形式监听到消息,从而能够做到篡改的效果,所以在 postMessag e和 onmessage 中一定要做好这方面的限制;
  • 发送的数据会通过结构化克隆算法进行序列化,所以只有满足该算法要求的参数才能够被解析,否则会报错,如function就不能当作参数进行传递;

LocalStorage

文章开头说了,浏览器的同源策略会导致,LocalStorage有同源限制。

解决办法:通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。

References

动手实现一个JSONP
前端跨域问题及解决方案
利用window.name+iframe跨域获取数据详解
跨域资源共享 CORS 详解

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant