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

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(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中的代码:

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);

};

在子页面中通过message事件监听父页面发送来的消息并显示。child.com/index.html中的代码:

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);

栗子2:

假设在a.html里嵌套个

在这两个页面里互相通信

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

...

function callback(data) {

console.log(data.url)

}

...

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,否则报错。

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.正式请求过了预检请求,非简单请求的正式请求就与简单请求一样了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,360评论 5 461
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,195评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,543评论 0 323
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,420评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,242评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,224评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,626评论 3 384
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,301评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,589评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,660评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,423评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,286评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,662评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,951评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,237评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,613评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,804评论 2 335

推荐阅读更多精彩内容