鸦杀's Blog

跨域整理

2016-07-22

这个问题每次面试有70%的概率会被问到。。。

在此做下整理,其实真正有用到过的就只有hxr2的cros,jsonp,图片ping,document.domain+iframe。至于form+iframe与document.name以及iframe+hash还有navigator都是今天整理的时候才发现

参考:

何谓同源:
URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
同源策略:
浏览器的同源策略,限制了来自不同源的”document”或脚本,对当前”document”读取或设置某些属性。 (白帽子讲web安全[1])
从一个域上加载的脚本不允许访问另外一个域的文档属性。

举个例子:
    比如一个恶意网站的页面通过iframe嵌入了银行的登录页面(二者不同源),如果没有同源限制,恶意网页上的javascript脚本就可以在用户登录银行的时候获取用户名和密码。

在浏览器中,<script>、<img>、<iframe>、<link>等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了JavaScript的权限使其不能读、写加载的内容。
另外同源策略只对网页的HTML文档做了限制,对加载的其他静态资源如javascript、css、图片等仍然认为属于同源。

1、xhr2的cros方案(ie6-7不兼容)

服务端请求头设置Access-Control-Allow-Origin:’‘。如果跨域需要带cookie,前端的xhr对象设置xhrFields:{WithCredentials:true},后端的Access-Control-Allow-Origin不能为,且要加上Access-Control-Allow-Credentials:true。

2、jsonp(只能使用get方式)-原理是动态创建javascript

3、postMessage

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

子窗口向父窗口传递消息:window.postMessage,
父窗口向子窗口传递消息:element.contentWindow.postMessage,
接收消息都监听message事件。

事件对象包含下面三个属性:
event.data: 消息
event.origin: 消息来源地址
event.source: 源 DOMWindow 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//data,部分浏览器只支持传字符串(ie8/9不支持对象)  
//origin,协议+域名+端口号+url

otherWindow.postMessage(message, targetOrigin);



var onmessage = function (event) {
if(e.source!=window.parent) return;
var data = event.data;
var origin = event.origin;
//do someing
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage);
}

4、document-domain-iframe(如果不同主域的话需要借助第三方页面)

(1)是同主域下面,不同子域之间的跨域:

  同主域,不同子域跨域,设置相同的document.domian就可以解决;

父页访问子页,可以document.getElementById("myframe").contentWindow.document来访问iframe页面的内容;如果支持contentDocument也可以直接document.getElementById("myframe").contentDocument访问子页面内容;

  子页访问父页,可以parent.js全局属性

(2) 是不同主域跨域;

  前提,www.a.com下a.html,a.html内iframe调用了www.b.com下的b.html,b.html下iframe调用了www.a.com下的c.html

  b.html是不无法直接访问a.html的对象,因为涉及到跨域,但可以访问parent,同样c.html的parent可以访问b.html。c.html和a.html同域,是可以访问a下的对象的。parent.parent.js对象!

5、利用iframe和location-hash

数据直接暴露在了url中,数据容量和类型都有限,且不安全。实现方案是父页面操作iframe的url,动态修改其hash值,子页面通过定时器检测到hash变化

b.html将数据以hash值的方式附加到c.html的url上,在c.html页面通过location.hash获取数据后传到a.html(这个例子是传到a.html的hash上,当然也可以传到其他地方)

a.html

1
2
3
4
5
6
7
8
9
10
11
var iframe = document.createElement("iframe");  
iframe.src = "http://www.b.com/b.html";
document.body.appendChild(iframe); // 在a页面引用b
function check() { // 设置个定时器不断监控hash的变化,hash一变说明数据传过来了
var hashs = window.location.hash;
if (hashs) {
clearInterval(time);
alert(hashs.substring(1));
}
}
var time = setInterval(check, 30);

b.html

1
2
3
4
5
6
window.onload = function() {  
var data = "this is b's data";
var iframe = document.createElement("iframe");
iframe.src = "http://www.a.com/c.html#" + data;
document.body.appendChild(iframe); // 将数据附加在c.html的hash上
}

c.html

1
2
// 获取自身的hash再传到a.html的hash里,数据传输完毕  
parent.parent.location.hash = self.location.hash.substring(1);

6、Flash-LocalConnection

对象可在一个 SWF 文件中或多个 SWF 文件间进行通信, 只要在同一客户端就行,跨应用程序, 可以跨域。

7、window-name-iframe

a.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 var iframe = document.createElement("iframe");  
iframe.src = "http://www.b.com/b.html";
document.body.appendChild(iframe); // 现在a.html里建一个引用b.html的iframe,获得b的数据

var flag = true;
iframe.onload = function() {
if (flag) {
iframe.src = "c.html";
// 判断是第一次载入的话,设置代理c.html使和a.html在同目录同源,这样才能在下面的else取到data
flag = false;
} else { // 第二次载入由于a和c同源,a可以直接获取c的window.name
alert(iframe.contentWindow.name);

iframe.contentWindow.close();
document.body.removeChild(iframe);
iframe.src = '';
iframe = null;
}
}

b.html

1
window.name = "这是 b 页面的数据";

8、用IE6-7中对navigator的bug:来自知乎

在ie6/7中,父子页面使用的window.navigator是同一个东西,父页面改了,子页面也会跟着变;

9、form-隐藏的iframe

a.com html:

1
2
3
4
5
<script>  
function postCallback(data){}
script>
<form action='接口链接' method='post' target='ifr'>form>
<iframe name='ifr'>iframe>

a.com callback.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  
header('Content-type: text/javascript');
echo ''</span>;</span>
<span class="line"><span class="comment">//回调原页面上函数处理返回结果</span></span>
<span class="line"><span class="keyword">echo</span> <span class="string">'window.top.postcallback('</span> .$_GET[<span class="string">'data'</span>]. <span class="string">');'</span>;</span>
<span class="line"><span class="keyword">echo</span> <span class="string">'';

?>












b.com api.php:














//....
$data = '{"ret":0,"msg":"ok"}';
// ** 让结果跳转到a.com域 **
header("Location: http://a.com/callback.php?data=".urlencode($data));
?>

10-proxy代理,将要请求的接口通过url参数发给后端,后端处理完后返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>proxy_testtitle>

<script>
var f = function(data){
alert(data.name);
}
var xhr = new XMLHttpRequest();
xhr.onload = function(){
alert(xhr.responseText);
};
xhr.open('POST', 'http://localhost:8888/proxy?http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer', true);
xhr.send("f=json");
script>
head>

<body>
body>
html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var proxyUrl = "";  
if (req.url.indexOf('?') > -1) {
proxyUrl = req.url.substr(req.url.indexOf('?') + 1);
console.log(proxyUrl);
}
if (req.method === 'GET') {
request.get(proxyUrl).pipe(res);
} else if (req.method === 'POST') {
var post = ''; //定义了一个post变量,用于暂存请求体的信息

req.on('data', function(chunk){ //通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
post += chunk;
});

req.on('end', function(){ //在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
post = qs.parse(post);
request({
method: 'POST',
url: proxyUrl,
form: post
}).pipe(res);
});
}

11、IE-的-XDomainRequest

只支持IE8/9,支持post/get,但是不支持不同协议的跨域。
xdr = new XDomainRequest(); 用法跟xhr差不多。

12、img天然支持跨域。但是只能发送数据。

13、cookie

Cookie中的同源只关注域名,忽略协议和端口。所以https://localhost:8080/和http://localhost:8081/的Cookie是共享的。

14、apache反向代理

使用代理方式跨域更加直接,因为SOP的限制是浏览器实现的。如果请求不是从浏览器发起的,就不存在跨域问题了。
使用本方法跨域步骤如下:

  1. 把访问其它域的请求替换为本域的请求
  2. 本域的请求是服务器端的动态脚本负责转发实际的请求
    各种服务器的Reverse Proxy功能都可以非常方便的实现请求的转发,如Apache httpd + mod_proxy。
    Eg.
    为了通过Ajax从http://localhost:8080访问http://localhost:8081/api,可以将请求发往http://localhost:8080/api。
    然后利用Apache Web服务器的Reverse Proxy功能做如下配置:
    ProxyPass /api http://localhost:8081/api

扫描二维码,分享此文章