同源战略与JS跨域(JSONP , CORS)

本文根据政治问答题必备套路分为以下3个部份:

  1. 为何要跨域?
  2. 跨域是什么?
  3. 怎样完成跨域?

Section1、为何要跨域?

自古以来(1995年起),为了用户的信息平安,浏览器就引入了同源战略。那末同源战略是怎样保证用户的信息平安的呢?

栗子1:假如没有同源战略,你翻开了你的银行账户页面A,又翻开了另一个不相干的页面B,这时候候假如B是歹意网站,B能够经由过程Javascript轻松接见和修正A页面中的内容。

栗子2:如今我们普遍的运用cookie来保护用户的登录状况,而假如没有同源战略,这些cookie信息就会泄漏,其他网站就能够假装这个登录用户。

由此能够看出,同源战略确实是必不可少的,那末它会带来哪些限定呢?

1、Cookie、LocalStorage和IndexDB没法读取。
2、DOM没法取得。
3、AJAX要求不能发送。

有时候我们须要打破上述限定,就须要用跨域的要领来处置惩罚。

Section2、跨域是什么?

什么叫做差异的域?比方:

http://www.a.com:8000/a.js
协定(http)、域名(www.a.com)、端口(8000)三者中有一个差异就叫差异的域。

跨域就是差异的域间相互接见时运用某些要领来打破上述限定。

【注重:协定或许端口的差异,只能经由过程背景来处置惩罚。】

Section3、怎样跨域?

一、处置惩罚Section1中提到的1、2两点限定:

1.Cookie、LocalStorage和IndexDB没法读取。
2.DOM没法取得。

要领1、经由过程document.domain跨子域

【适用范围:(1)两个域只是子域差异;(2)只适用于iframe窗口与父窗口之间相互猎取cookie和DOM节点,不能打破LocalStorage和IndexDB的限定。】

当两个差异的域只是子域差异时,能够经由过程把document.domain设置为他们配合的父域来处置惩罚。

栗子:

A: http://www.example.com/a.html
B: http://example.com/b.html
当A、B想要猎取对方的cookie或许DOM节点时,能够设置:

document.domain='example.com';

来处置惩罚。

这时候A网页经由过程剧本设置:

document.cookie = "testA=hello";

B网页就能够拿到这个cookie:

var aCookie = document.cookie;

要领2、经由过程window.name跨域

【适用范围:(1)能够是两个完整差异源的域;(2)统一个窗口内:即统一个标签页内前后翻开的窗口。】

pre-condition:

window.name属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的一切的页面都是同享一个window.name的,每一个页面临window.name都有读写的权限,window.name是耐久存在一个窗口载入过的一切页面中的。

基于这个头脑,我们能够在某个页面设置好 window.name 的值,然后在本标签页内跳转到别的一个域下的页面。在这个页面中就能够猎取到我们方才设置的 window.name 了。

连系iframe另有更高等的用法:

父窗口先翻开一个与本身差异源的子窗口,在这个子窗口里设置:

window.name = data;

然后让子窗口跳转到一个与父窗口同域的网址:

location='http://www.parent.com/a.html';

这时候,因为同域而且统一窗口window.name是稳定的,所以父窗口能够猎取到子窗口下的window.name。

var data = document.getElementById('myFrame').contentWindow.name;

长处:window.name容量很大,能够安排异常长的字符串;瑕玷:必需监听子窗口window.name属性的变化,影响网页机能。

要领3、运用HTML5的window.postMessage跨域

window.postMessage(message,targetOrigin) 要领是html5新引进的特征,能够运用它来向别的的window对象发送音讯,不管这个window对象是属于同源或差异源,现在IE8+、FireFox、Chrome、Opera等浏览器都已支撑window.postMessage要领。

otherWindow.postMessage(message, targetOrigin);

otherWindow:接收音讯页面的window的援用。能够是页面中iframe的contentWindow属性;window.open的返回值;经由过程name或下标从window.frames取到的值。
message:所要发送的数据,string范例。
targetOrigin:用于限定otherWindow,*示意不做限定。

栗子1:

在父页面中嵌入子页面,经由过程postMessage发送数据。
parent.com/index.html中的代码:

<iframe id="ifr" src="child.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
    var ifr = document.getElementById('ifr');
    var targetOrigin = 'http://child.com'; 
    // 若写成'http://child.com/c/proxy.html'结果一样
    // 若写成'http://c.com'就不会实行postMessage了
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

在子页面中经由过程message事宜监听父页面发送来的音讯并显现。
child.com/index.html中的代码:

<script type="text/javascript">
window.addEventListener('message', function(event){
    // 经由过程origin属性推断音讯来源地点
    if (event.origin == 'http://parent.com') {
        alert(event.data);    // 弹出"I was there!"
        alert(event.source);  
        // 对parent.com、index.html中window对象的援用
        // 但因为同源战略,这里event.source不能够接见window对象
    }
}, false);
</script>

栗子2:

假设在a.html里嵌套个

<iframe src="http://www.child.com/b.html" frameborder="0"></iframe>

在这两个页面里相互通讯

a.html

window.onload = function() {
    window.addEventListener("message", function(e) {
        alert(e.data);
    });

    window.frames[0].postMessage("b data", "http://www.child.com/b.html");
}

b.html

window.onload = function() {
    window.addEventListener("message", function(e) {
        alert(e.data);
    });
    window.parent.postMessage("a data", "http://www.parent.com/a.html");
}

如许翻开a页面,起首监听到了b.html经由过程postMessage传来的音讯,就先弹出 a data,然后a经由过程postMessage通报音讯给子页面b.html,这时候会弹出 b data。

二、处置惩罚第3点限定:

3)AJAX要求不能发送。

要领4、经由过程JSONP跨域

【适用范围:(1)能够是两个完整差异源的域;(2)只支撑HTTP要求中的GET体式格局;(3)老式浏览器悉数支撑;(4)须要服务端支撑】

JSONP(JSON with Padding)是材料花样JSON的一种运用形式,能够让网页从别的网域要材料。

因为浏览器的同源战略,在网页端涌现了这个“跨域”的题目,然则我们发明,一切的 src 属性并没有遭到相干的限定,比方 img / script 等。

JSONP 的道理就要从 script 提及。script 能够援用其他域的剧本文件,比方如许:

a.html
...
<script>
    function callback(data) {
        console.log(data.url)
    }
</script>
<script src='b.js'></script>
...

b.js
callback({url: 'http://www.rccoder.net'})

这就类似于JSONP的道理了。

JSONP的基础头脑是:先在网页上增加一个script标签,设置这个script标签的src属性用于向服务器要求JSON数据 ,须要注重的是,src属性的查询字符串一定要加一个callback参数,用来指定回调函数的名字 。而这个函数是在资本加载之前就已在前端定义好的,这个函数接收一个参数并应用这个参数做一些事变。向服务器要求后,服务器会将JSON数据放在一个指定名字的回调函数里作为其参数传返来。这时候,因为函数已在前端定义好了,所以会直接挪用。

一个栗子:

function addScriptTag(src) {
    var script = document.createElement('script');
    script.setAttribute("type","text/javascript");
    script.src = src;
    document.body.appendChild(script);
}

window.onload = function () {
    addScriptTag('http://example.com/ip?callback=foo');//要求服务器数据并划定回调函数为foo
}

function foo(data) {
    console.log('Your public IP address is: ' + data.ip);
};

向服务器example.com要求数据,这时候服务器会先天生JSON数据,这里是{“ip”: “8.8.8.8”},然后以JS语法的体式格局天生一个函数,函数名就是通报上来的callback参数的值,末了将数据放在函数的参数中返回:

foo({
    "ip": "8.8.8.8"
});

客户端剖析script标签,实行返回的JS代码,挪用函数。

要领5、经由过程CORS跨域

【适用范围:(1)能够是两个完整差异源的域;(2)支撑一切范例的HTTP要求;(3)被绝大多数当代浏览器支撑,老式浏览器不支撑;(4)须要服务端支撑】

关于前端开发者来讲,跨域的CORS通讯与同源的AJAX通讯没有差异,代码完整一样。因而,完成CORS通讯的症结是服务器。只需服务器完成了CORS接口,就能够跨源通讯。

浏览器将CORS要求分红两类:简朴要求(simple request)和非简朴要求(not-so-simple request)。

只需同时满足以下两大前提,就属于简朴要求。

(1) 要求要领是以下三种要领之一:
HEAD
GET
POST
(2)HTTP的头信息不超越以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

通常差异时满足上面两个前提,就属于非简朴要求。
浏览器对这两种要求的处置惩罚,是不一样的。

简朴要求:

下面是一次跨源AJAX要求,浏览器发明它是简朴要求,就会直接在头信息中加一个origin字段:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

服务器收到这条要求,假如这个origin指定的源在许可范围内,那末服务器返回的头信息中会包括Access-Control-Allow-Origin字段,值与origin的值雷同,以及其他几个相干字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar

Access-Control-Allow-Origin: 该字段是必需的。要么与origin雷同,要么为*
Access-Control-Allow-Credentials: 该字段可选。设为true示意服务器许可发送cookie
Access-Control-Expose-Headers: 该字段可选。CORS要求时,XMLHttpRequest对象的getResponseHeader()要领只能拿到6个基础字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。假如想拿到其他字段,就必需在Access-Control-Expose-Headers内里指定。上面的例子指定,getResponseHeader(‘FooBar’)能够返回FooBar字段的值。

想要发送cookie,这里另有两点须要分外注重:

1)开发者必需在AJAX要求中翻开withCredentials属性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

不然纵然服务器许可,客户端也不会发送。

2)Access-Control-Allow-Origin不能设为星号,必需指定明白的、与要求网页一致的域名。同时,Cookie依旧遵照同源政策,只要效服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也没法读取服务器域名下的Cookie。

非简朴要求:

1.预检要求:

非简朴要求会在正式通讯前加一次预检(preflight)要求。作用是浏览器先讯问服务器当前网页地点域名是不是在服务器的许可名单中,以及能够运用哪些HTTP要领以及头信息字段。只要获得一定回复,浏览器才会发送XMLHttpRequest,不然报错。

一个栗子:

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

HTTP要求要领为PUT,并发送一个自定义头信息”X-Custom-Header”,浏览器发明这是一个非简朴要求,就会自动发送一个预检要求,预检要求的HTTP头信息以下:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

要求要领是OPTIONS,示意这个要求是用来讯问的,头信息中的症结信息有3个:

(1)示意要求来自哪一个源

Origin: http://api.bob.com

(2)列出浏览器的CORS要求会用到哪些HTTP要领


Access-Control-Request-Method: PUT

(3)指定浏览器CORS要求会分外发送的头信息字段

Access-Control-Request-Headers: X-Custom-Header

2.预检要求的回应(有两种状况:A许可、B不许可)

A.服务器许可此次跨域要求

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

服务器返回中要注重的字段:

(1)服务器赞同的跨域要求源:

Access-Control-Allow-Origin: http://api.bob.com

(2)服务器支撑的一切跨域要求的要领:

 Access-Control-Allow-Methods: GET, POST, PUT   

(3)表明服务器支撑的一切头信息字段:

Access-Control-Allow-Headers: X-Custom-Header

(4)指定本次预检要求的有效期,单元为秒,即许可要求该条回应在有效期之前都不必再发送预检要求:

Access-Control-Max-Age: 1728000

B.服务器不许可此次跨域要求
即origin指定的源不在许可范围内,服务器会返回一个一般的HTTP回应。然则头信息中没有包括Access-Control-Allow-Origin字段,就晓得出错了,从而抛出一个毛病,被XMLHttpRequest的onerror回调函数捕捉。然则要注重的是,这类HTTP回应的状况码很有多是200,所以没法经由过程状况码辨认这类毛病。

3.正式要求
过了预检要求,非简朴要求的正式要求就与简朴要求一样了。

参考材料

  1. 《Javascript高等程序设计》 P582
  2. 《Javascript威望指南》 P668 22.3 跨域音讯通报
  3. http://www.ruanyifeng.com/blo…
  4. http://www.ruanyifeng.com/blo…
  5. https://segmentfault.com/a/11…
  6. https://segmentfault.com/a/11…
  7. https://segmentfault.com/a/11…
  8. http://www.cnblogs.com/rainma…
    原文作者:一只瓦罐
    原文地址: https://segmentfault.com/a/1190000009624849
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞