同源策略与跨域技术
Posted bonnie3449
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同源策略与跨域技术相关的知识,希望对你有一定的参考价值。
待整理: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
浏览器的同源策略限制了 从一个源加载的文档或脚本与来自另一个源的资源的交互。它是隔离潜在恶意文档的关键安全机制。
具体限制:
- 不能通过ajax的方法去请求不同源的资源。
- 浏览器中不同域的框架之间是不能进行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的同源检测。
注意:
- 只能将当前域设置为其父域,不能设置为其他域,如store.company.com可以设置为company.com,不能设置为othercompany.com
- 对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
- 使用document.domain来修改子域域名以访问其父域时,需要在父域和子域中设置document.domain都为父域的值。这样做是必要的,原因除了2.中所述原因以外,还因为不这样做可能会导致权限问题。
- 修改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:WindowsSystem32driversetchosts文件,加上一行:
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
以上是关于同源策略与跨域技术的主要内容,如果未能解决你的问题,请参考以下文章