注:跨域的知识点详见:跨域相关知识点
目录
实验验证环境配置:
1、利用document.domain降域
方法1:
方法2:
2、利用location.hash
3、利用window.name
4、利用postMessage(最推荐)
使用postmessage实现跨域访问
使用postmessage读取其他窗口的localstorage(普通款)
使用postmessage读取其他窗口的localstorage(加强版本):
我们一共需要配置五个虚拟主机,即需要使用五个域名
www.aaa.com、www.bbb.com、master.security.com、slave.security.com、www.security.com
(1)在本地的WWW目录下创建两个文件夹
cross_orgin和cross_orgin_sub
(2)域名分配:
cross_orgin:www.aaa.com
cross_orgin_sub:www.bbb.com
为了方便我们访问,这里需要在本地配置虚拟主机
(3)虚拟主机:
DocumentRoot "F:/PHPstudy/phpstudy_pro/WWW/openlab/cross_orgin"
ServerName www.aaa.com
FcgidInitialEnv PHPRC "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts"
AddHandler fcgid-script .php
FcgidWrapper "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php
Options FollowSymLinks ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
DirectoryIndex index.php index.html
DocumentRoot "F:/PHPstudy/phpstudy_pro/WWW/openlab/cross_orgin_sub"
ServerName www.bbb.com
FcgidInitialEnv PHPRC "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts"
AddHandler fcgid-script .php
FcgidWrapper "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php
Options FollowSymLinks ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
DirectoryIndex index.php index.html
这里配置了两个虚拟主机:www.aaa.com 和 www.bbb.com分别对应的是我们所配置的
cross_orgin和cross_orgin_sub两个目录文件夹
(4)在vhost中配置ip与域名的对应关系
然后我们需要在本地环境的C:\Windows\System32\drivers\etc 目录下的vhost文件中增加对应关系
127.0.0.1 www.aaa.com
127.0.0.1 www.bbb.com
(5)我们可以在 cross_orgin和cross_orgin_sub 文件夹中任意创建页面文件,然后尝试使用域名进行访问
如果使用域名访问看到了编辑的页面内容,说明我们的环境已经搭建好了。
下面就详细的介绍一下ifame实现跨域DOM互访问的四种方式:
(6)
然后使用上面同样的方式完成master.security.com和slave.security.com和www.security,com这三个域名的搭建
这三个域名对应的是本地的cookie_orgin和cookie_orgin_sub、CSSinject文件夹
这里就只提供这三个域名的虚拟主机配置文件:
DocumentRoot "F:/PHPstudy/phpstudy_pro/WWW/openlab/cookie_orgin"
ServerName master.security.com
FcgidInitialEnv PHPRC "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts"
AddHandler fcgid-script .php
FcgidWrapper "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php
Options FollowSymLinks ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
DirectoryIndex index.php index.html
DocumentRoot "F:/PHPstudy/phpstudy_pro/WWW/openlab/cookie_orgin_sub"
ServerName slave.security.com
FcgidInitialEnv PHPRC "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts"
AddHandler fcgid-script .php
FcgidWrapper "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php
Options FollowSymLinks ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
DirectoryIndex index.php index.html
DocumentRoot "F:/PHPstudy/phpstudy_pro/WWW/openlab/CSSinject"
ServerName www.security.com
FcgidInitialEnv PHPRC "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts"
AddHandler fcgid-script .php
FcgidWrapper "F:/PHPstudy/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php
Options FollowSymLinks ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
DirectoryIndex index.php index.html
注:虚拟主机中的目录文件需要与自己本地的文件配置一致,不要直接使用我这个
master.security.com 的index.html文件:
方法1:
Master
master
这里将slave.security通过iframe嵌入到当前页面,并且这里在script标签中设置了document.domain,目的是让同样设置了这样的页面可以跨域互访,并还设置了一个document.cookie用于测试
document.domain = 'security.com'//domain进行降域不看子域名只看主域
document.cookie = 'name=master'
slave.security.com的index.html文件 :
slave
slave
这里就是我们用来测试的页面与前面一样设置了document.domain,并且尝试弹出第一个文件设置的cookie值
将文件创建后以后,我们可以尝试使用访问一下slave.security.com测试:
可以看到这里确实弹出了cookie,但是并不是文件1中设置的cookie,所以这种方法并没有实现真正的跨域互访DOM
master.security.com 的index.html文件 :
Master
master
这里和方法1一样设置了document.domain,但是不同的是,这里使用onload事件,必须要等到ifame加载完成后,在执行子页面的内容,然后这里还尝试将子页面的data尝试弹窗显示出来
slave.security.com 的index.html文件:
slave
slave
这里也是设置了与前面相同document.doamin,与方法1不同的是,这里没有了弹窗,反而定义了一全局变量
然后我们就可以尝试访问matser.security.com测试:
可以看到成功的弹窗了,并且弹出阿里data的值正是子页面定义的值,所以这里成功利用domain降域实现了跨域DOM互访
总:方法1在这个实验在浏览器中已经无法实现了,方法2可以正常实现
(1)aaa.com(index.html文件)
var ifr = document.createElement('iframe')
ifr.src = 'http://www.bbb.com#data';
ifr.style.display = 'none';
document.body.appendChild(ifr);
function checkHash() {
try {
let data = location.hash ? location.hash.substring(1) : ' ';
console.log('获取到的数据为:', data);
} catch (e) {
}
}
checkHash();
window.addEventListener('hashchange', function (e) {
console.log('获取到的数据为:', location.hash.substring(1));
});
这里首先将www.bbb.com子页面引入到本页面,并且增加一个锚点data,然后checkHash函数中进行了异常处理,try中对data进行判断,判断这是不是一个location.hash,如果是则取出#后的数据,不是就置为空,然后打印出data的数据
后面再进行了对hashchange的监听
(2)bbb.com(index.html文件)
switch (location.hash) { // 判断location1哈希
case "#data":
callback(); //调用函数
break;
}
function callback() {
const data = "some number:11111"
try {
parent.location.hash = data; //把自定义的值赋值给父页面的哈希值(即,改变了父页面的哈希)
} catch (e) {
//利用一个中间代理页面ifame(cs3.html)
//要求:需要和第一个页面是同源
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = "http://www.aaa.com/c.html#" + data;
document.body.appendChild(ifrproxy);
}
}
//获取父页面中的body标签中的内容
这里对location.hash进行判断,如果是#data则调用回调函数,回调函数中对data进行了赋值,并且将该值赋值给了父级的location.hash,然后后面利用了c.html作为中间代理页面,将data值传给父级
(3)中间代理c.html文件
parent.parent.location.hash = self.location.hash.substring(1)
这里就是将自己的location.hash中后面#中的值,赋值给父级的父级也就是bbb.com的父级aaa.com
(4)测试
我们尝试访问一下www.aaa.com
可以看到我们成功的使用c.html作为中间代理,使用www.aaa.com访问到了www.bbb.com中的值,实现了跨域DOM互访的目的
缺点:
数据直接暴露在URL中
数据容量和类型都有限
总:这种方法已经被淘汰了
windows.name(一般在js代码中出现)的值不是一个普通的全局变量,而是当前窗口的名字,要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,而它自然也有window.name的属性。
window.name的属性的神奇之处就在于name的值在不同的页面(甚至不同的域名)加载后依然存在(如果没有修改则值不会变化),并且可以支持非常长的name值(2MB)
举一个简单的例子:你在某个页面的控制台输入:
window.name="hello world"
window.location="http://www.baidu.com"
页面跳转到了百度的页面,但是window.name却被保留下来,还是hello world
那么我们现在可以利用window.name这一性质来尝试进行跨域访问
(1)security.com(window_name.html)
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = "http://www.aaa.com";
document.body.appendChild(ifr);
ifr.onload = function () {
ifr.onload = function () {
let data = ifr.contentWindow.name;
console.info(data);
}
ifr.src = "http://www.security.com/c.html"
}
这里的windo_name.html文件中使用iframe将www.aaa.com作为子页面引入到当前页面,然后使用加载时间,来获取当前页面的,window.name值,并且打印出来,后面还指定了监听源为:www.security.com/c.html
(2)www.aaa.com(index.html)
window.name = "hello aaaa";
这里只需要定义一个window.name即可
(7)c.html
空
(8)测试
可以看到这里成功的访问到了www.aaa.com中定义的window.name的值,并且,当前的Window.name的值为' '
总:当www.security.com/window_name.html在请求远端服务器www.aaa.com的数据时,我们可以在该页面下新建一个iframe,该iframe的src属性指向服务器地址(利用iframe标签的跨域能力),服务器文件aaa.com设置好了window.name值
但是由于window_name页面与aaa.com页面的iframe的src不同源的话,则无法操作iframe里面的任何东西,所以就取不到iframe的name值,所以我们只需要在aaa.com加载完成后重新换一个src指向同一个源的html文件,或者设置about:blank都行,这时候我们只要在window_name相同的目录下创建一个c.html空白文件即可,如果不重新指定src的话直接获取window.name就会报错
注:以上的方式均属于hack
(1)security.com
window.onload = function () {
let targetOrigin = 'http://www.aaa.com'; //想要去的地址,即给它发送消息
window.frames[0].postMessage('向aaa.com发送消息', targetOrigin);
}
window.addEventListener('message', function (e) {
console.log('security.com接收到的消息', e.data);
});
这里使用了一个事件监听着一个函数,函数中的tarOrigin指向www.aaa.com,这里表示为发送消息的接收地址,后面使用Postmessage向aaa.com发送消息,后面监听了消息这个事件
(2)aaa.com
方案1:
window.addEventListener('message', function (e) {
if (e.source != window.parent)
return;
let data = e.data;
console.log("aaa.com接收到的消息", data);
// window.frames[0].postMessage("向aaa.com发消息", targetOrgin);
parent.postMessage('aaa.com发送security', e.origin);
}, false);
这里定义了一个监听事件,监听消息,如果消息的源不是父级传递来的,则直接返回,否则将父级消息赋值给data,并且打印出来,然后后面也使用了postmessage向www.security发送消息
方案2:
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== 'http://www.security.com') return;
//事件的源地址
if (event.data === 'Hello World') {
//事件的内容
event.source.postMessage('Hello', event.origin);
} else {
console.log(event.data);
}
}
这里的实现是使用了event的几个属性来实现的
(3)测试
方案1:
可以看到,www.aaa.com和www.security.com都互相收到了跨域发送的消息
方案2:
这里可以看到www.security也成功的接收到了ww.aaa发送的消息
(1)security/index.html
window.onload = function () {
let targetOrigin = 'http://www.aaa.com'; //想要去的地址,即给它发送消息
var obj = { name: '杨攀帅', age: 20, 'wigth': 100 }
window.frames[0].postMessage(
JSON.stringify({ key: 'storage', method: 'set', data: obj }), targetOrigin
);
}
window.addEventListener('message', function (e) {
console.log('security.com接收到的消息', e.data);
});
(2)aaa.com/index.html
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== 'http://www.security.com') return; //信息来源判断
var payload = JSON.parse(event.data);
localStorage.setItem(payload.key, JSON.stringify(payload.data))
}
(3)测试
可以看到,aaa.com页面成功使用postmessage读取到了security.com页面中的数据,并且存储到了本地
(1)security/index.html
window.onload = function () {
let targetOrigin = 'http://www.aaa.com'; //想要去的地址,即给它发送消息
var obj = { name: 'yps', age: 20, 'wigth': 100 }
//加强版
window.frames[0].postMessage(
JSON.stringify({ key: 'storage', method: 'set', data: obj }),
'http://www.aaa.com'
);
window.frames[0].postMessage(
JSON.stringify({ key: 'storage', method: 'get' }),
'*'
);
window.frames[0].postMessage(
JSON.stringify({ key: 'storage', method: 'remove' }),
'*'
);
}
window.onmessage = function (e) {
if (e.origin != 'http://www.aaa.com') return;
console.log(JSON.parse(e.data).name
);
};
这里的大致思路和上面一样,那为什么说它是一个加强版本呢,因为,这里我们可以自己选择时set、还是get、还是remove Messageage发送的消息
需要注意的是,要想让数据存储下来,需要将remove注释掉,否则将无法正常的读取到数据
(2)aaa.com/index.html
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== "http://www.security.com") return;
var payload = JSON.parse(event.data)
switch (payload.method) {
case 'set':
localStorage.setItem(payload.key, JSON.stringify(payload.data))
break;
case 'get':
var parent = window.parent;
var data = localStorage.getItem(payload.key)
parent.postMessage(data, 'http://www.security.com/')
break;
case 'remove':
localStorage.removeItem(payload.key)
break;
default:
break;
}
}
这里也是增加了set、get、remove的选项看,并且完成了对应操作的实现
(3)测试
这里也是一样的,可以看到,aaa.com页面成功使用postmessage读取到了security.com页面中的数据,并且存储到了本地
注:如果在将数据存入数据中没有转换/存储时转换为JSON格式时会报错
到这里使用iframe实现跨域DOM互访的几种方法已经介绍完毕了,更多跨域的内容请看下篇分享