同源策略及其解决方案

“同源政策”是浏览器安全的基石,其设计目的是为了保证信息安全,防止恶意的网站窃取数据。所谓“同源”必须知足如下三个方面:javascript

  1. 协议相同
  2. 域名相同
  3. 端口相同(默认端口是80,能够省略)

若是是非同源的,如下行为会受到限制:php

  • Cookie、LocalStorageIndexDB没法读取
  • DOM没法获取
  • AJAX请求不能发送

接下来咱们主要讲解如何解决以上三个方面的问题。html

1、Cookie

Cookie只有同源的网站才能获取,可是若是两个网页的一级域名相同,只是二级域名不一样,能够设置相同的document.domain,两个网页就能够共享cookie了。java

不少人都误把带www当成一级域名,把其余前缀的当成二级域名,是错误的。正确的域名划分为:json

  1. 顶级域名:.com
  2. 一级域名:baidu.com
  3. 二级域名:tieba.baidu.com

举例来讲,A网页是http://w1.sillywa.com/a.html,B网页是http://w2.sillywa.com/b.html,咱们能够设置跨域

document.domain = 'sillywa.com'
复制代码

这样两个网页就能够共享Cookie了。浏览器

注意,这种方法只是用于CookieiframeLocalStorageIndexDB没法经过这种方法规避同源政策,而是要是用PostMessage API,下面咱们会介绍。安全

2、iframe

若是两个网页不一样源,就无法拿到对方的DOM。典型的例子是iframe窗口和用window.open方法打开的窗口,它们与父窗口没法通讯。bash

因此对于彻底不一样源的网站,目前可使用一下三种办法规避同源问题:服务器

  • 片断标识符(fragment identifier)
  • window.name
  • 跨文档通讯API(window.postMessage)

1.片断标识符

片断标识符指的是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)
}
复制代码

2.window.name

浏览器窗口有window.name属性。这个属性的最大特色是,不管是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页能够读取它。

3. window.postMessage

HTML5为了解决跨窗口通信问题引入了一个新的API:跨文档通讯API。这个APIwindow新增了一个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对象有如下三个属性:

  1. event.source:发送消息的窗口
  2. event.origin:消息发送的网址
  3. event.data:消息内容

下面的例子是,子窗口经过event.source属性引用父窗口,而后发送消息。

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  event.source.postMessage('Nice to see you!', '*');
}
复制代码

若是咱们将发送的消息改成LocalStorage,则能够互相读取LocalStorage

3、AJAX

一样AJAX请求也会受到同源策略的影响,除了使用代理服务器外,还有一下方法能够实现跨域:

  • jsonp
  • WebScoket
  • CORS

1.jsonp

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是必需的。

2.WebScoket

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)
}
复制代码

在这三个事件中只有closeevent对象有额外的信息。这个事件的对象有三个额外的属性:wasClean、code、reason。其中wasClean是一个布尔值,表示链接是否已经明确地关闭;code是服务器返回的数值状态码;reason是一个字符串,包含服务器发回的信息。

3.CORS

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高级程序设计》

相关文章
相关标签/搜索