“同源政策”是浏览器安全的基石,其设计目的是为了保证信息安全,防止恶意的网站窃取数据。所谓“同源”必须知足如下三个方面:javascript
若是是非同源的,如下行为会受到限制:php
Cookie、LocalStorage
和IndexDB
没法读取DOM
没法获取AJAX
请求不能发送接下来咱们主要讲解如何解决以上三个方面的问题。html
Cookie
只有同源的网站才能获取,可是若是两个网页的一级域名相同,只是二级域名不一样,能够设置相同的document.domain
,两个网页就能够共享cookie
了。java
不少人都误把带
www
当成一级域名,把其余前缀的当成二级域名,是错误的。正确的域名划分为:json
- 顶级域名:
.com
- 一级域名:
baidu.com
- 二级域名:
tieba.baidu.com
举例来讲,A网页是
http://w1.sillywa.com/a.html
,B网页是http://w2.sillywa.com/b.html
,咱们能够设置跨域
document.domain = 'sillywa.com'
复制代码
这样两个网页就能够共享Cookie
了。浏览器
注意,这种方法只是用于Cookie
和iframe
,LocalStorage
和IndexDB
没法经过这种方法规避同源政策,而是要是用PostMessage API
,下面咱们会介绍。安全
若是两个网页不一样源,就无法拿到对方的DOM
。典型的例子是iframe
窗口和用window.open
方法打开的窗口,它们与父窗口没法通讯。bash
因此对于彻底不一样源的网站,目前可使用一下三种办法规避同源问题:服务器
fragment identifier
)window.name
API
(window.postMessage
)片断标识符指的是URL
中#
后面的内容,好比http://sillywa.com/a.html#fragment
中的#fragment
,若是只是改变片断标识符,页面不会从新刷新。
父窗口能够把信息写入子窗口的片断标识符:
var src = originURL + '#' + data
document.getElementById('myIframe').src = src
复制代码
子窗口经过监听hashchange
事件获得通知:
window.onhashchange = function() {
console.log(window.location.hash)
}
复制代码
浏览器窗口有window.name
属性。这个属性的最大特色是,不管是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页能够读取它。
HTML5
为了解决跨窗口通信问题引入了一个新的API
:跨文档通讯API
。这个API
为window
新增了一个window.postMessage()
方法,容许跨窗口通信,不论这两个窗口是否同源。举例来讲:假设父窗口为:http://aaa.com
,子窗口为:http://bbb.com
// 父窗口向子窗口发送消息
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
复制代码
postMessage()
方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin
),即"协议 + 域名 + 端口"。也能够设为*,表示不限制域名,向全部窗口发送。
一样,子窗口向父窗口发送消息能够这样写:
window.opener.postMessage('Nice to see you', 'http://aaa.com');
复制代码
父窗口和子窗口均可以经过message
事件,监听对方的消息:
window.addEventListener('message', function(e) {
console.log(e.data)
},false)
复制代码
message
事件的event
对象有如下三个属性:
event.source:
发送消息的窗口event.origin:
消息发送的网址event.data:
消息内容下面的例子是,子窗口经过event.source
属性引用父窗口,而后发送消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*');
}
复制代码
若是咱们将发送的消息改成LocalStorage
,则能够互相读取LocalStorage
。
一样AJAX
请求也会受到同源策略的影响,除了使用代理服务器外,还有一下方法能够实现跨域:
jsonp
WebScoket
CORS
jsonp
想必你们都很了解,其由两部分组成:回调函数和数据。其基本思路是:动态插入script
标签,向服务器请求json
数据,返回的数据将在回调函数里得到。
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
// 定义回调函数
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
复制代码
上面代码经过动态添加<script>
元素,向服务器example.com
发出请求。注意,该请求的查询字符串有一个callback
参数,用来指定回调函数的名字,这对于JSONP
是必需的。
WebScoket
不一样于http
,它提供一种双向通信的功能,即客户端能够向服务器请求数据,同时服务器也能够向客户端发送数据。而http
只能是单向的。
同时WebScoket
使用ws:\//
(非加密)和wss:\//
(加密)做为协议前缀。该协议不实行同源政策,只要服务器支持,就能够经过它进行跨源通讯。
要建立WebScoket
,先实例化一个WebScoket
对象并传入要链接的URL
:
var scoket = new WebScoket("ws://www.example.com/server.php")
复制代码
实例化WebScoket
对象以后,浏览器会立刻尝试创建链接。与XHR
相似,WebScoket
也有一系列表示当前状态的readyState
属性,以下:
WebScoket.OPENING
(0):正在创建链接WebScoket.OPEN
(1):已经创建链接WebScoket.CLOSING
(2):正在关闭链接WebScoket.ClOSE
(3):已经关闭链接WebScoket
没有readyStatechange
事件;不过它有其余的事件,咱们待会介绍。
要关闭WebScoket
链接,能够调用close()
方法:
scoket.close()
复制代码
WebScoket
链接以后,就能够发送和就收数据。要发送数据能够调用send()
方法,并传入字符串,例如:
var scoket = new WebScoket("ws://www.example.com/server.php")
scoket.send('hello word')
复制代码
由于WebScoket
只能发送纯文本数据,因此对于复杂的数据类型咱们应先将其序列化转化为json字符串
var message = {
name: 'sillywa'
}
scoket.send(JSON.stringify(message))
复制代码
一样服务器必须先解析再读取数据。
当服务器向客户端发来消息时,WebScoket
对象就会触发message
事件。这个message
事件与其它传递消息的协议相似,也就是把返回的数据保存在event.data
的属性中。
scoket.onmessage = function(event) {
console.log(event.data)
}
复制代码
与经过send()
发送到服务器的数据同样,event.data
中返回的数据也是字符串。
WebScoket
对象还有其余三个事件,在链接生命周期的不一样阶段触发。
open:
在成功创建链接时触发error
:在发生错误时触发,链接不能持续close:
在链接关闭时触发 WebScoket
对象不支持DOM2
级事件侦听器,所以必须使用DOM0
级语法分别定义每一个事件处理程序。var scoket = new WebScoket("ws://www.example.com/server.php")
scoket.onopen = function() {
console.log('connection start')
}
scoket.onerror = function() {
console.log('connection error')
}
scoket.onclose = function(event) {
console.log(event)
}
复制代码
在这三个事件中只有close
的event
对象有额外的信息。这个事件的对象有三个额外的属性:wasClean、code、reason
。其中wasClean
是一个布尔值,表示链接是否已经明确地关闭;code
是服务器返回的数值状态码;reason
是一个字符串,包含服务器发回的信息。
CORS
是一个W3C
标准,全称是"跨域资源共享"(Cross-origin resource sharing
)。
它容许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX
只能同源使用的限制。
相比jsonp
只能发送get
请求,CORS
容许发送任何类型的请求。但CORS
要求浏览器和服务器同时支持。目前全部浏览器都支持,IE
须要IE10
以上。
整个CORS
通信过程当中都是浏览器自动完成,不须要用户的参与。CORS
通信和同源的AJAX
请求没有区别。浏览器一旦发现AJAX
请求跨域,就会自动添加一些头部信息,有时候还会多出一次附加请求。
浏览器将CORS
请求分为两类:简单请求和非简单请求。
只要同时知足一下两个条件就是简单请求,不然就是非简单请求:
(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
对于简单请求,浏览器会自动在头部信息里增长一个Origin
字段,用来表示请求来自与哪一个源,服务器根据这个值决定是否赞成这次请求。若是Origin
不在请求范围内,服务器返回一个正常的http
回应。这个回应的头信息中没有Access-Control-Allow-Origin
字段,浏览器发现没有这个字段以后就会抛出一个错误。若是Origin
在请求范围内,服务器返回的响应会多出几个头信息字段,其中一个是Access-Control-Allow-Origin
,它的值要么是Origin
的值,要么是*,表示容许任何域名的请求。
对于非简单请求,它会在正式通讯以前,增长一次http
查询请求,称为"预检"请求(preflight
)。一般是一个OPTION
请求。这个请求先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可使用哪http
动词和头信息字段。只有获得确定答复,浏览器才会发出正式的XMLHttpRequest
请求,不然就报错。
若是你们想要更详细的了解CORS
,能够参考如下文章。
参考文章:
阮一峰《浏览器同源政策及其规避方法》
阮一峰《跨域资源共享 CORS 详解》
参考书籍:
《javascript高级程序设计》