首页 > 其他 > 详细

同源策略与跨域技术

时间:2018-07-20 18:41:38      阅读:173      评论:0      收藏:0      [点我收藏+]

待整理:1-2参见《JavaScript高级程序设计》P586;3-5参见http://mp.weixin.qq.com/s/asmzA8a1HuYQxyx8K0q-9g

一、同源策略

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

浏览器的同源策略限制了 从一个源加载的文档或脚本与来自另一个源的资源的交互。它是隔离潜在恶意文档的关键安全机制。

具体限制:

  1. 不能通过ajax的方法去请求不同源的资源。
  2. 浏览器中不同域的框架之间是不能进行js的交互操作的。

1. 同源的定义

如果两个页面具有相同的协议、域名和端口(如果有指定),则这两个页面具有相同的源。

Tips:http协议默认端口是80,https默认端口是443。

2. 源的更改

脚本可以将 document.domain的值设置为当前域或当前域的父域。如果设置为超级域,那么超级域将用于后续的源检查。

Eg:对页面 http://store.company.com/dir/other.html 进行域的修改:

document.domain = ‘company.com‘;

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

注意:

  1. 只能将当前域设置为其父域,不能设置为其他域,如store.company.com可以设置为company.com,不能设置为othercompany.com
  2. 对document.domain的赋值操作会导致端口号被重写为null。所以store.company.com:8080即使设置了document.domain = ‘company.com‘,也还是不能和company.com通信。因为此时store.company.com的域名虽然是company.com,但端口号是null;而company.com的端口号可能是80;二者端口号不一致,还是不同源。所以必须在页面company.com也进行document.domain = document.domain,即双方都必须进行赋值操作,以确保端口号都为null
  3. 使用document.domain来修改子域域名以访问其父域时,需要在父域和子域中设置document.domain都为父域的值。这样做是必要的,原因除了2.中所述原因以外,还因为不这样做可能会导致权限问题。
  4. 修改document.domain的方法 只适用于不同子域的框架(即iframe/frame)间的交互不能用于Ajax——即使设置了相同的document.domain,还是不能进行Ajax请求

3.正常的跨域网络访问

通常允许跨源资源嵌入(Cross-origin embedding)。

有以下这些情况:

(1)script标签嵌入跨源脚本

<script src="...">
</script>

(2)rel=stylesheet的link标签嵌入css

<link rel="stylesheet" href="...">

css跨域需要设置一个正确的Content-Type消息头。不同浏览器有不同限制。一般都可以成功跨源获取css资源。

(3)img嵌入图片,video/audio/object嵌入多媒体资源

(4) @font-face引入字体

一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。

(5)frame和iframe载入的任何资源

iframe本身就是可以跨域的

站点可以使用 X-Frame-Origins消息头来阻止这种跨域。

Tips:关于X-Frame-Origins

X-Frame-Origins,是一个HTTP响应头,用来指示浏览器是否允许一个页面可以在iframe/frame/object中展示。网站可以使用此功能,来确保自己网站的内容没有被嵌到别人的网站中去。

可能值:

  • DENY:不允许该页面在其他页面的frame/iframe中展示,无论那个其他页面和该页面是否是同源。
  • SAMEORIGIN:该页面可以在同源页面的frame/iframe中展示。
  • ALLOW-FROM uri:该页面可以在指定源的页面的frame/iframe中展示。
测试
Test1: 嵌入baidu.com:
<iframe width="1000" height="800" src="https://www.baidu.com"></iframe>

该页面可以正确地嵌入百度首页。但是控制台会报如下错误信息:

 Uncaught DOMException: Blocked a frame with origin "https://www.baidu.com" from accessing a cross-origin frame
  at HTMLDocument.t...

可以发现,这些跨域错误信息都是由于需要进行js交互才出现的。

Test2: 嵌入ftchinese.com:
<iframe width="1000" height="800" src="http://www.ftchinese.com"></iframe>

iframe区域展现的是空白。然后console控制台报错的信息为:

Refused to display ‘http://www.ftchinese.com/‘ in a frame because it set ‘X-Frame-Options‘ to ‘deny‘.

4. 不允许的跨域网络访问

通常不允许跨域读操作(Cross-origin reads)。一般不能通过ajax的方法去请求不同源的资源。 浏览器中不同域的框架之间也是不能进行js的交互操作的。

但是通常可以通过内嵌资源等方式来巧妙的进行读写访问。Ajax经过特殊设置也可以实现跨域Ajax通信。不同源的框架间在一定条件限制下也可以通过一定手段实现js交互。

二、跨域技术

1. CORS

理论概述

CORS(Cross-Origin Resource Sharing,跨域资源共享)定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。

CORS的基本思想是设置某些HTTP头部字段让浏览器和服务器进行沟通,从而决定请求或响应是应该成功还是失败。

需要关注的HTTP头部字段:
  • 请求头 Origin:在发送跨域请求时,请求头会附加一个Origin字段,其值是发送请求的页面的源(包括协议、域名、端口)。具体操作:
    • 如果使用XMLHttpRequest,那么在xhr.send()方法中填入请求目标地址的绝对url即可;
    • 如果使用Fetch,那么需要设置请求参数‘mode‘为‘cors‘。
  • 响应头 Access-Control-Allow-Origin: 如果服务器认为该请求该跨域请求可以被接受,就为响应设置Access-Control-Allow-Origin响应头。具体操作为:
    • 如果将其设置为‘*‘, 则表明来自所有源的请求都可以被接受;
    • 如果将其设置为 对应请求的源的url,即请求的Origin字段值,则表明针对来自该源的请求可以被允许。
CORS标准允许的常见的使用跨域请求的场景有:
  • XMLHttpRequest发起的跨域HTTP请求
  • Fetch发起的跨域HTTP请求
CORS请求的特点:

请求和响应都 默认不包含cookie信息

就是说跨源请求不提供凭据(包括cookie、HTTP认证及客户端SSL证明等)。

如果跨源请求需要发送凭据,那么解决办法为:

  • 客户端若使用XMLHttpRequst,那么需要设置 xhr.withCredentials为true;若使用Fetch,那么需要设置请求参数 credentials为‘include‘。

  • 服务端设置响应头 Access-Control-Allow-Credentials为true

实践:XMLHttpRequest发起跨源请求

发送Ajax请求的页面地址为:http://localhost:3000/a;
请求目标的地址为:http://sub.localhost:3001/b, 该地址提供一段json数据。

页面a客户端代码:

<div>我是a</div>
<button type="button" id="sendBtn">点我发送Ajax请求</button>
<script>
  const sendBtn = document.getElementById(‘sendBtn‘);
  sendBtn.addEventListener(‘click‘, function() {
    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);
        }
      }
    }
    xhr.open(‘get‘, ‘{{reqDest}}‘, true);
    xhr.send(null);
  });

页面b的服务端重点代码(by koa):

  router.get(‘/b‘, ctx => {
    ctx.set(‘Access-Control-Allow-Origin‘,‘http://localhost:3000‘);
    ctx.body = {
      ‘name‘:‘bonne‘,
      ‘age‘:26
    }
  });

现象:

在a页面点击按钮可以看到控制台输出了‘{"name":"bonne","age":26}‘,即成功地获取到了跨源数据资源。

实践: Fetch发起跨源请求

将a页面请求代码做如下修改:

<div>我是a</div>
<button type="button" id="sendBtn">点我发送Ajax请求</button>
<script>
  const sendBtn = document.getElementById(‘sendBtn‘);
  sendBtn.addEventListener(‘click‘, function() {
    fetch(‘{{reqDest}}‘, {
      mode: ‘cors‘
    }).then( res => {
      if (res.ok) {
        return res.json();
      } else {
        throw new Error(‘Network response was not ok‘);
      }
    }).then( resData => {
      console.log(resData);
    }).catch(err => {
      console.Error(err.message);
    })
  });
</script>

其他不变。

现象:

在a页面点击按钮依然可以看到控制台输出了‘{"name":"bonne","age":26}‘,即使用该Fetch方式也成功地获取到了跨源数据资源。

2.图像Ping

理论概述

我们都知道,img标签可以从任何网页中加载图像,无论是否跨域。图像Ping就是利用了img标签的这一功能。

图像Ping是与服务器进行简单、单向的跨域通信的一种方式。数据可以通过src地址的查询字符串发送到服务器。浏览器可以通过监听load和error事件,判断服务器是何时接收到响应。

实践:图像ping帮助客户追踪广告曝光次数

最常用于跟踪用户点击页面的行为或广告曝光次数。

例如我们网站就是使用图像Ping给广告客户的服务器发送图像Ping来是的广告客户获取广告曝光次数的数据:

var track = new Image();
track.onload = function() {
    window.parent.ga(‘send‘, ‘event‘, ‘iPhone web app launch ad‘, ‘Sent‘, imp, {‘nonInteraction‘:1});
};     
track.onerror = function() {
    window.parent.ga(‘send‘, ‘event‘, ‘iPhone web app launch ad‘, ‘Fail‘, imp, {‘nonInteraction‘:1});
};
track.src = imp;//imp为广告客户的广告曝光追踪地址,其实是一个白色小圆点图片

3.JSONP

理论概述

script元素和img类似,都有能力不受限制地从其他域加载资源。JSONP就是利用了script元素的这一功能。

JSONP是JSON with Padding(参数式JSON或填充式JSON),就是被包含在函数中调用的JSON。

JSONP由两部分组成:数据回调函数。 数据就是传入回调函数中的JSON数据。

JSONP的工作过程:为script标签的src指定一个跨域的URL(即JSONP服务的地址),并在URL中指定回调函数名称。因为JSONP服务最终返回的是有效的JavaScript代码,请求完成后会立即执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以,jsonp是需要服务器端进行相应的配合的

示例:用koa和中间件koa-jsonp实现jsonP服务

发起jsonp请求的前端页面相关代码为:

 <script>
    function doSomething(jsonpData) {
      console.log(jsonpData);
    }
  </script>
  <script src="http://localhost:3000/?cb=doSomething"></script>

使用动态方式加载script亦可:

<script>
  function doSomething(jsonpData) {
    console.log(jsonpData);
  }
  var scriptElem = document.createElement(‘script‘);
  script.src = ‘http://localhost:3000/?cb=doSomething‘;
  document.body.append(scriptElem);
<script>

cb就是url中指定回调函数名称的参数,通常是callback,这个是需要在服务端设置的。

展示该前端页面的服务代码:

const path = require(‘path‘);
const Koa = require(‘koa‘);
const Router = require(‘koa-router‘);
const logger = require(‘koa-logger‘);
const views = require(‘koa-views‘);

const app = new Koa();
const router = new Router();

app.use(logger());

app.use(views(path.resolve(__dirname,‘views‘)));

async function showText(ctx) {
  await ctx.render(‘test‘);
}
router.get(‘/‘, showText);

app.use(router.routes());

app.listen(3001, () => {
  console.log(‘Listening 3001‘);
});

jsonp服务的代码:

const Koa = require(‘koa‘);
const Router = require(‘koa-router‘);
const jsonp = require(‘koa-jsonp‘);
const logger = require(‘koa-logger‘);
const app = new Koa();
const router = new Router();

app.use(logger());
app.use(jsonp({
  callbackName:‘cb‘//指定回调函数名称的参数, defaults to ‘callback‘
}));

router.get(‘/‘, ctx => {
   ctx.body = {
    name:‘Bonnie‘,
    age:26
  }
});

app.use(router.routes());

app.listen(3000, () => {
  console.log(‘Listening 3000‘);
});

页面的端口为3001,jsonp服务端口为3000,形成跨域,但是页面可以完美获取到jsonpData。

具体可参见我写的用koa和中间件koa-jsonp实现jsonP服务的例子:

4.通过修改document.domain来跨域

理论概述

浏览器中不同域的框架之间是不能进行js的交互操作的。脚本试图访问的框架内容必须遵守同源策略。也就是说:

对于同源的框架来说:

不同域的框架之间可以进行js交互。

  • 父页面访问子页面:通过 contentWindow属性,父页面的脚本可以访问iframe元素所包含的子页面的window对象。contentDocument属性则引用了iframe中的文档元素(等同于使用contentWindow.document),但IE8-不支持。

  • 子页面访问父页面:通过访问 window.parent,脚本可以从框架中引用它的父框架的window。

对于非同源的框架来说:

脚本无法访问非同源的window对象的几乎所有属性。

该同源策略即适于父窗体访问子窗体的window对象,也适用于子窗体访问父窗体的window对象。

实践:同源和跨源iframe交互操作的验证

(1)验证同源的框架间js互相访问window对象毫无障碍:

页面a的地址为http://localhost:3000/a;
页面b的地址为http://localhost:3000/b;
页面a中通过iframe嵌入页面b。

页面a代码:

<div>我是a</div>
<script>
  window.name = ‘parentFrame‘;
  window.globalvarA = ‘aaa‘;
  function onLoad() {
    const otherFrame = document.getElementById(‘otherFrame‘);
    console.log(‘Parent console: Values of props from the child frame window:‘)
    console.log(`win:${otherFrame.contentWindow}`);
    console.log(`window.postMessage:${otherFrame.contentWindow.postMessage}`);

    console.log(`dom:${otherFrame.contentWindow.document}`);
    console.log(`name:${otherFrame.contentWindow.name}`);
    console.log(`globalvarB:${otherFrame.contentWindow.globalvarB}`);
  }
</script>
<iframe id="otherFrame" name="otherFrame" src=‘http://localhost:3000/b‘ onload="onLoad()"></iframe>

页面b代码:

  <div>我是b</div>
  <script>
    window.globalvarB = ‘bbb‘;
    console.log(‘Child console:Values of props from the parent frame window:‘)
    console.log(`win:${window.parent}`);
    console.log(`win.postMessage:${window.parent.postMessage}`);
    console.log(`dom:${window.parent.document}`);
    console.log(`name:${window.parent.name}`);
    console.log(`globalvarA:${window.parent.globalvarA}`);
  </script> 

在http://localhost:3000/a的浏览器窗口可以看到a页面正确载入了b页面的内容。

在http://localhost:3000/a的控制台可以看到,a和b框架都有输出,b框架输出在a框架之前:

b框架输出结果:

  Child console:Values of props from the parent frame window:
  win:[object Window]
  win.postMessage:function () { [native code] }
  dom:[object HTMLDocument]
  name:parentFrame
  globalvarA:aaa

a框架输出结果:

  win:[object Window]
  window.postMessage:function () { [native code] }
  dom:[object HTMLDocument]
  name:otherFrame
  globalvarB:bbb

可见同源的父子框架之间js互相访问window对象确实非常顺畅。

(2)验证不同源的框架间js互相访问window对象存在障碍:

页面a的地址为http://localhost:3000/a;
页面b的地址为http://sub.localhost:3001/b;
页面a、b的嵌套关系不变,代码也不变,除了a中iframe的src值修改为b的新地址http://sub.localhost:3001/b

页面a代码:

<div>我是a</div>
<script>
  window.name = ‘parentFrame‘;
  window.globalvarA = ‘aaa‘;
  function onLoad() {
    const otherFrame = document.getElementById(‘otherFrame‘);
    console.log(‘Parent console: Values of props from the child frame window:‘)
    try {
      console.log(`win:${otherFrame.contentWindow}`);
    } catch(err) {
      console.log(‘cannot get otherFrame.contentWindow‘);
    }

    try {
      console.log(`window.postMessage:${otherFrame.contentWindow.postMessage}`);
    } catch(err) {
      console.log(‘cannot get otherFrame.contentWindow.postMessage‘);
    }
    
    try {
      console.log(`dom:${otherFrame.contentWindow.document}`);
    } catch(err) {
      console.log(‘cannot get otherFrame.contentWindow.document‘);
    }

    try {
      console.log(`name:${otherFrame.contentWindow.name}`);
    } catch(err) {
      console.log(‘cannot get otherFrame.contentWindow.name‘);
    }

    try {
      console.log(`globalvarB:${otherFrame.contentWindow.globalvarB}`);
    } catch(err) {
      console.log(‘cannot get otherFrame.contentWindow.glovalvarB‘);
    }
  }
</script>
<iframe id="otherFrame" name="otherFrame" src=‘http://sub.localhost:3001/b‘ onload="onLoad()"></iframe>

页面b代码:

<div>我是b</div>
  <script>
    window.globalvarB = ‘bbb‘;
    console.log(‘Child console:Values of props from the parent frame window:‘)
    try {
      console.log(`win:${window.parent}`);
    } catch(err) {
      console.log(‘cannot get window.parent‘);
    }

    try {
      console.log(`window.postMessage:${window.parent.postMessage}`);
    } catch(err) {
      console.log(‘cannot get window.parent.postMessage‘);
    }
    
    try {
      console.log(`dom:${window.parent.document}`);
    } catch(err) {
      console.log(‘cannot get window.parent.document‘);
    }

    try {
      console.log(`name:${window.parent.name}`);
    } catch(err) {
      console.log(‘cannot get window.parent.name‘);
    }

    try {
      console.log(`globalvarB:${window.parent.globalvarA}`);
    } catch(err) {
      console.log(‘cannot get window.parent.globalvarA‘);
    }
  </script>

Tips1: koa中间件koa-subdomain可以完成对子域名的划分。

Tips2:使用try{} catch() {}可以在报错的时候不影响后续代码执行,所以这里把每个window相关属性的获取都放在try{} catch(){}语句中

现象与结论

(1) 在http://localhost:3000/a的浏览器窗口, 可以看到a页面依然正确载入了b页面的内容。
由此可以再次说明,使用iframe载入html页面本身是可以跨域的(如果没有对 ‘X-Frame-Options‘响应头进行限制)。

(2) 在http://localhost:3000/a控制台,可以看到:

b框架输出:

Child console:Values of props from the parent frame window:
cannot get window.parent
window.postMessage:function () { [native code] }
cannot get window.parent.document
cannot get window.parent.name
cannot get window.parent.globalvarA

框架a输出:

Parent console: Values of props from the child frame window:
cannot get otherFrame.contentWindow
window.postMessage:function () { [native code] }
cannot get otherFrame.contentWindow.document
cannot get otherFrame.contentWindow.name
cannot get otherFrame.contentWindow.glovalvarB

可见:

  • 不同源的父子框架之间js 不能获取到对方window对象及其属性和方法
  • html5中的postMessage方法是一个例外,即便 获取不到对方的window对象,也能获取到对方的window.postMessage方法

参见我写的同源和跨源iframe交互操作的示例

实践:修改document.domain来实现跨域

将页面的 document.domain的值设置为当前域或当前域的父域。详见 一、中 2. 源的更改。

注意: 使用此方法实现跨域仅针对不同框架间js的交互有效,对于Ajax还是无效。

在上述a.html和b.html中的script标签中的第一行加上:

document.domain = ‘localhost‘

理论上在本地测试应该可以成功了。但事实上会报错:

Failed to set document.domain to localhost. ‘localhost‘ is a top-level domain

其实是因为localhost这个域名很特殊,这样设置不合法。

解决办法是通过修改C:\Windows\System32\drivers\etc\hosts文件,加上一行:

127.0.0.1   test.com

将localhost换成合法域名。

修改域名后,a可以通过http://test.com:3000/a访问。
但是,koa-subdomain中间件会失效,访问b还是只有http://sub.localhost:3001/b,http://sub.test.com:3001/b并不会生效。

如果想要看到跨域结果,还是去非本地的服务器上测试吧~~~

5.使用window.name来进行跨域

原理概述

window有一个属性name。window.name用于获取或设置window的名称。

window.name的特性:在一个window的生命周期内,该window载入的所有页面都是共享一个window.name的。每个被载入的页面对该window.name都有读写的权限。如果新载入的页面没有对window.name进行重写,那么每一个新载入的页面都可以获取到相同的window.name。

实践:对上述准备知识的验证

页面a的地址为http://localhost:3000/a;
页面b的地址为http://localhost:3000/b;
在页面a中过5s将window.location改为页面b的地址。

页面a代码:

<div>我是a</div>
<script>
  window.name = ‘页面a‘;
  setTimeout(function() {
    window.location = ‘http://localhost:3000/b‘;
  }, 5000);
</script>

页面b代码:


<div>我是b</div>
  <script>
     console.log(window.name);
  </script>

在http://localhost:3000/a可以看到5s过后载入了页面b,且控制台输出‘页面a‘。即window.name并没有因为载入新的页面b而发生变化。

将a、b页面的地址做如下修改:

页面a的地址为http://localhost:3000/a;
页面b的地址为http://sub.localhost:3001/b。

看到的结果和之前一样。

所以,对于一个window, window.name不会因为window.location的改变而改变(除非新载入的页面修改了这个值),无论新载入的页面和之前的页面是否存在跨域

NOTE: window.name的值只能是 字符串的形式,这个字符串的大小最大能允许 2M左右甚至更大的一个容量,具体取决于不同的浏览器,但一般是够用了。

跨域原理

如果上述a.html和b.html是跨域的,我们知道a在通过修改window.location的方式载入b后,b依然可以获取之前a的window.name,可是这样a页面自己的内容已经丢失了。那么现在假如b的window.name里存储有我们需要的数据,如何在a页面中获得来自b的window.name的数据呢?

我们可以在a中通过一个隐藏的iframe引入页面b。iframe本身是可以跨域的,所以这一点不用担心。然而我们需要的是获取这个iframe的name,因为跨域所以无法进行js交互,所以自然也无法获取b的window.name。但是我们可以利用上述window.name的特性,在a中用js将该iframe的src修改为一个同源的页面地址(假设为c),这样就可以获取c的window.name了。又因为这个iframe之前是b,只是重新又载入了c,所以c的window.name就是之前b的window.name,所以a就可以通过获取c的window.name获取b的window.name了。

实践代码如下:

实践:使用window.name实现跨域

页面a的地址为http://localhost:3000/a;
页面b的地址为http://sub.localhost:3001/b;
页面c的地址为http://localhost:3000/c

页面a代码:

 <div>我是a</div>
  <iframe style="display: none;" id="dataSource" src=‘http://sub.localhost:3001/b‘></iframe>
  <script>
    const dataSourceIframe = document.getElementById(‘dataSource‘);
    dataSourceIframe.onload = function() {
      dataSourceIframe.onload = function() {
        const data = dataSourceIframe.contentWindow.name;
        console.log(data);
      }
      dataSourceIframe.src = ‘/c‘;
    }
  </script>

页面b代码:

  <div>我是b</div>
  <script>
     window.name = JSON.stringify({
       name:‘Bonnie‘,
       age:26
     })
  </script>

在http://localhost:3000/a的控制台打印出了数据 {"name":"Bonnie","age":26},实现了跨域。

tips: 每修改一次iframe的src都会触发一次iframe的onload事件。

具体代码参见我写的通过window.name实现跨域的例子。

5.使用HTML5中新引进的window.postMessage方法来跨域传送数据

跨域原理

window.postMessage() 是html5引进的新方法,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源

通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议、端口号、主机时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就可以安全地实现跨源通信。

postMessage

发送数据的页面调用postMessage方法:

otherWindow.postMessage(message, targetOrigin, [transfer]);

params:

  • otherWindow: 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。

  • message:将要发送到otherWindow的数据。可以是string或object。

  • targetOrigin: 指定哪些window能接收到消息事件,即otherWindow的地址,其值可以是字符串"*"(表示无限制)或者一个URI。 **如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。**

  • transfer (可选): 一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后向目标window派发一个 MessageEvent 消息,即触发目标window的message事件。

message事件

接收数据的页面监听message事件。

message事件有的event对象有一些特殊的属性:

  • event.data: 从发送数据的 window 中传递过来的对象。

  • event.origin: 调用 postMessage 时消息发送方窗口的 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成。例如 “https://example.org (隐含端口 443)”、“http://example.net (隐含端口 80)”、“http://example.com:8080”。请注意,这个origin不能保证是该窗口的当前或未来origin,因为postMessage被调用后可能被导航到不同的位置。

  • event.source:对发送数据的window对象的引用。使用它可以在具有不同origin的两个窗口之间建立双向通信。

实践:使用window.postMessage实现跨域

页面a的地址为http://localhost:3000/a;
页面b的地址为http://sub.localhost:3001/b;

页面a代码:

<div>我是a</div>
<script>
  const data = {
    name:‘Bonnie‘,
    age:26
  }
  function onLoad() {
    const otherFrame = document.getElementById(‘otherFrame‘);
    otherFrame.contentWindow.postMessage(data,‘http://sub.localhost:3001‘);
  }
</script>
<iframe id="otherFrame" name="otherFrame" src=‘http://sub.localhost:3001/b‘ onload="onLoad()"></iframe>

页面b代码:

  <div>我是b</div>
  <div id="messageResult"></div>
  <script>
     window.onmessage = function(e) {
       const messageResult = document.getElementById(‘messageResult‘);
       messageResult.innerHTML = JSON.stringify(e.data);
       console.log(e.data);
       console.log(e.origin);
       console.log(e.source);
     }
  </script>

在http://localhost:3000/a的浏览器窗口可以看到加载的页面b中的messageresult部分输出了正确的数据。

在http://localhost:3000/a的控制台,可以看到b框架的输出:

{name: "Bonnie", age: 26}
http://localhost:3000 
global {window: global, self: global, location: Location, closed: false, frames: global, …}

即 e.origin是 http://localhost:3000 , e.source是global {window: global, self: global, location: Location, closed: false, frames: global, …}

具体可参见我写的使用postMessage实现跨域的例子。

6. Web Sockets

理论概述

Web Sockets是一种基于ws协议的技术。使用它可以在客户端和服务器之间建立一个单独的、持久的、全双工的、双向的通信。

在JavaScript中创建了Web Socket之后,会有一个HTTP请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议换为Web Socket协议。 也就是说,标准的HTTP服务器无法实现Web Sockets,只有支持Web Socket协议的服务器才能实现

由于Web Sockets使用了 自定义协议,所以其URL的模式也有一些不同。未加密的连接是 ws://,而非http:// ; 加密的连接是 wss://, 而非https://。

Web Sockets的特点
  • 连接持久
  • 全双工通信
  • 通信数据开销小:使用自定义协议而非HTTP协议,使得客户端和服务器之间可以发送非常少的数据,而不必像HTTP那样是字节级的开销。
  • 不受同源策略限制:可以通过它打开到任何站点的连接

而HTTP的特点是:

  • 单向通信
  • 无连接(响应头 Connection: keep-alive 可以使连接持续有效)
  • 无状态
示例代码
var socket=new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");
socket.onmessage=function(event){
    var data=event.data;
    //处理数据,可以用这些数据更新页面的某部分
}

参考文档与博客

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe

https://www.jianshu.com/p/b587dd1b7086
https://mp.weixin.qq.com/s/asmzA8a1HuYQxyx8K0q-9g?

https://www.techwalla.com/articles/how-to-change-your-local-host-name

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

《JavaScript高级程序设计》21.5

同源策略与跨域技术

原文:https://www.cnblogs.com/Bonnie3449/p/9342950.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!