跨域以及一些解决方法

跨域

最近在回顾一些知识,概括一下之前的笔记再结合各个资料说一下我对跨域和跨域问题的解决方法。 产生跨域安全问题不是后台服务器不容许前台调用, 其本质是浏览器的同源策略(Same-origin policy)形成的,它是浏览器最基本和最核心的安全机制,同源是指URI schemehost nameport number相同,借用一下网上的栗子:php

http://www.bear.cn/index.html 调用 http://www.bear.cn/server.php 非跨域

http://www.bear.cn/index.html 调用 http://www.jasmine .cn/server.php 跨域,主域不一样

http://gogo.bear.cn/index.html 调用 http://ge.jasmine.cn/server.php 跨域,子域名不一样

http://www.bear.cn:2018/index.html 调用 http://www.bear.cn/server.php 跨域,端口不一样

https://www.bear.cn/index.html 调用 http://www.bear.cn/server.php 跨域,协议不一样
复制代码

若是非同源,将会受到以下限制:html

  • Cookie、LocalStorage 和 IndexDB 没法读取。
  • DOM 没法得到。
  • AJAX 请求不能发送。

浏览器发现前台代码发出了一个非本域的请求,出于安全的考虑,浏览器会作一些校验,若是校验不经过,就没法完成这个请求,抛出请求跨域的错误 json

Jsonp

JSONP是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种办法,JSONP看起来和JSON差很少,只不过是被包含在函数调用中的JSON,就像这样:后端

callback({"name": "Nicholas Bear"})
复制代码

JSONP由两部分组成:回调函数和数据。回调函数是当浏览器接收到响应时调用的函数,回电函数名通常在请求中指定,数据就是回调函数的参数。以下就是典型的JSONP请求:跨域

http://somewhere-else/json/?callback=handleResponse
复制代码

这里指定的回调函数就是handleResponse()浏览器

JSONP实现原理是经过JS脚本动态生成一个script元素,为其src属性指定一个跨域URL,这里的script元素和img、link元素相似,都有能力不受限制地从其余域加载资源。它并非官方的协议,而是一种hack手段,看一个简单的栗子:缓存

function handleResponse(res) {
    alert("got message", res);
}
var script = document.createElement("script"),
    body = document.body;
script.src = "http://somewhere-else/json/?callback=handleResponse";
body.insertBefore(script, body.firstChild);
复制代码

JSONP实现跨域访问很是方便,简单易用,可是也有不足的地方:安全

首先,从它的实现方式能够看出来,它是发起一个资源获取请求,是GET类型的,在平常开发中经常使用的请求类型还有POSTPUTDELETE,而JSONP只能发起GET请求,是它的一大短板。服务器

其次,JSONP是从其余域中加载代码并执行,若是其余域不安全,颇有可能会在执行的代码中夹杂一些恶意代码,因此在使用JSONP时必定要保证被请求方它安全可靠。cookie

另外,JSON和JSONP还有一个区别须要特别注意,JSONP请求返回来的不是JSON数据,而是一个JavaScript脚本,为了实现JSONP跨域,须要后台服务器配合。

最后,因为它的请求类型并非XHR,就缺乏了一些事件处理程序,要追踪JSONP请求是否失败并不容易,或者为JSONP请求增长定时器,超时就视为请求失败,接下来就再次发送请求或者作其余事情,可是每一个用户的网络情况并不能保证,这样作也不是万全之策。

CORS

CORS(Cross-origin resource sharing)跨域源资源共享,是W3C的一个工做草案, 定义了在跨域访问时,浏览器与服务器的沟通方式,具体实现为,使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定跨域请求或响应时应该成功,仍是应该失败。

好比说发起一个GET跨域请求,Content-type是text/plain,在发送跨域请求前,浏览器会为http头部加上一个额外的Origin头部,其中包含了页面的源信息(协议、域名和端口号),这个额外的Origin决定了服务器是否响应该请求。一个Origin头部实例:

Origin: https://www.somewhere-else.net
复制代码

若是服务器承认该请求就会在响应头加上Access-Control-Allow-Origin标志字段,值能够是与请求头带来的Origin相同,若是该服务器上的是公共资源,值就是“*”。

Access-Control-Allow-Origin: https://www.somewhere-else.net
复制代码

若是响应头中没有这个这个字段,说明服务器拒绝了此次跨域请求,会抛出一个错误,可是并不能被xhr的onerror事件捕获。默认状况下跨域请求都是不带凭证的(cookie,HTTP认证及服务端SSL证实等),经过修改xhr对象的withCredentials(IE10之前的版本不支持该属性)设置为true,能够指定某个请求携带凭证。若是服务器容许跨域请求携带凭证响应头部会有标示。

Access-Control-Allow-Credentials: true
复制代码

若是发送的是带凭证的请求,响应头里却没有这个字段,那么浏览器就不会吧响应交给JS,意思是xhr获取到的responseText为空,status为0,这个时候onerror能够捕获到该错误.

XHR对象在跨域时也是有限制的:

  • 不能使用setRequestHeader()来设置头部
  • 默认状况下没法发送cookie
  • 调用getAllResponseHeaders()方法总会返回空字符串

CORS的实现:

var xhr = new XMLHttpRequest();
xhr.onreadystateChange = function() {
    if(xhr.readyState === 4) {
        if(xhr.status >= 200 && xhr.status <= 300 || xhr.status === 304) {
            alert(xhr.responseText);
        } else {
            alert("error ", xhr.status);
        }
    }
}
xhr.open("get", "http://www.somewhere-else.com/page", true);
xhr.send(null);
复制代码

发送CORS请求和发送普通的xhr对象差异不大, 只须要在地址处写绝对地址便可.跨域所须要作的工做就交给浏览器,对于用户来讲是透明.

IE浏览器是用XDR(XDomainRequest)来实现CORS的,它和XHR类似,可是能提供能安全可靠的跨域通讯:

  • cookie不会随请求发送,也不会随响应返回
  • 只能设置请求头部信息中的Content-Type字段
  • 不能访问响应头部信息
  • 只支持GETPOST请求

XDR对象和xhr的使用方法类型,也是创造一个XDomainRequest的实例,调用open()方法,再调用send()方法,可是与xhr对象的open()不一样,XDR对象的open()方法只接受两个参数:请求的类型和URL,XDR发送的请求都是异步执行的。并且XDR对象没法访问status属性,因此在使用XDR时必定得经过onerror事件处理程序来捕获错误.

简单请求

跨域请求在发送前,浏览器会检查这个请求是否是简单请求,简单请求知足下面两个条件:

  • 请求方式为HEAD,POST,GET
  • HTTP头部信息包括但不超过如下字段
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type(application/x-www-form-urlencode,multipart/form-data,text/plain)

若是知足这些条件,浏览器就会在请求头部增长额外的Origin字段后发送跨域请求。

响应头通常包含这些字段:

  • Access-Control-Allow-Origin,若是浏览器校验经过,这个字段显示的是请求头的Origin值或者*
  • Access-Control-Allow-Credential,值为布尔型,表示请求头是否能够携带cookie
  • Access-Control-Expose-Headers。拓展的头部信息,浏览器将CORS响应交给JS后,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其余字段,就必须在Access-Control-Expose-Headers里面指定。

注意,若是你想在请求中携带凭证,上面已经说过了,必须将xhr的withCrediential属性设置为true,但有时会报错,错误信息以下图:

错误提示里说若是想要在请求头中携带凭证,那么响应头中的 Access-Control-Allow-Origin必须和请求头中的Origin一致,而不能是“*”,解决方法很简单,修改一下后端代码就能够了。

非简单请求

CORS经过一种叫作Preflighted Requestes预请求的透明服务器验证机制支持开发人员使用自定义的头部,GET和POST以外的方法,以及不一样类型的主题内容。也就是说想要发送这种非简单的跨域请求之前会先发送一个询问请求(携带非简单请求部分信息)来询问服务器是否赞成此次非简单请求,这种询问请求使用OPTIONS方法,发送如下头部:

  • Origin:和简单请求相同
  • Access-Control-Request-Method:请求自身使用的方法
  • Access-Control-Request-Headers:这是一个可选头部字段,多个头部以逗号分开。

发送这个请求之后,服务器能够决定是否容许这种类型的请求。服务器能够经过在响应头中携带如下头部与浏览器沟通:

  • Access-Control-Allow-Origin:和简单请求相同
  • Access-Control-Allow-Methods:容许的方法
  • Access-Control-Allow-Headers: 容许的头部
  • Access-Control-Max-Age: 预请求的有效期或者缓存存活时间(秒)

好比说我如今发送了一个自定义头部字段f-headers1f-headers2,方法为post的非简单请求,那么首先发送的预请求头部会包含如下信息:

Origin: http://www.yourhostname.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: f-headers1, f-headers2
复制代码

若是服务器容许这样的非简单请求的跨域访问,返回的响应头会包含这些字段:

Access-Control-Allow-Origin: http://www.yourhostname.com
Access-Control-Allow-Method: POST,GET,PUT,DELETE
Access-Control-Allow-Headers: f-headers1, f-headers2
Access-Control-Max-Age: 3600
复制代码

预请求结束后,结果将按照响应中指定的时间缓存起来,下次再发送这样的非简单请求以前就不会再发送询问请求.

Cookie

上述几条都是解决跨域请求资源,可是若是想要获取非同源的cookie,LocalStorage或IndexDB怎么办。cookie是服务器在浏览器上写下的一小段认证信息,大小通常是4k,根据浏览器的不一样,每一个域容许种下的cookie数量也不一样。cookie只有在同源的域下才能共享,可是咱们能够经过修改document.domain来共享cookie,以下所示

// a.abc.com
document.domain = "abc.com";
document.cookie = "name=bingo";
// b.abc.com
document.domain = "abc.com";
console.log(document.cookie); // "name=bingo"
复制代码

可是这种方法前提是这两个网页一级域名相同,一级域名或者叫根域名相同是什么意思呢,好比说这里有个两个域名www.abc.comwww.f.abc.com它们的一级域名都是abc.com。二级域名就是增长了一级包括www,好比说www.zdt.com,netgo.ccdn.com,www.baidu.com等等.三级,四级域名同理.

并且这种方法只适用于cookie和iframe.没法获取locastorage和IndexDB.

iframe

利用iframe解决跨域问题也是一种可取的办法.光是给iframe增长src获取其余页面的资源是不现实,必须借助一些特性实现hack手段.

document.domain

两个iframe之间或者父窗口和子窗口之间。如上述例子里经过改变相同主域的document.domain能够跨域获取cookie,也能够获取对方的全局变量。这种方法和跨域获取cookie同样,只适合具备相同主域的跨域访问。实现原理为相同主域的网站设置相同的document.domain,浏览器就职务它们是同源的,这种方式比较简单,但也有安全问题,若是某一个网站被攻击后,另外一个网站就会有安全漏洞

window.name

window.name,它具备更新了页面的location更新后,值依然不会更变的神奇特性,这让咱们跨域访问信息提供了机会。在一个页面中建立一个不一样域的iframe,这个iframe的js代码修改它window.name的值,而后再将它变为和父窗口同域的iframe,在父窗口中就能够经过iframe得到修改事后的window.name的值

location.hash

location.hash又称片断标识符(Fragment Identitier),它是URL字符中#后面的部分,好比http://www.somewhere-else.com/a.html#fragment,这里的片断标识符就是fragment,URL中的片断标识符改变并不会引发页面刷新.利用location.hash实现跨域访问信息的原理是父窗口能够读写子窗口的URL,子窗口只能读写相同域父窗口的URL.这里想要实现跨域,不一样域的子窗口就必须借助一个与父窗口同域的代理. 举个栗子

a.abc.com/index.html(a)下有一个src为smg.com/index.html(b)的iframe.

1.a页面给b页面发送数据

  • a修改b的src为smg.com/index.html#data
  • b页面访问本身的location.hash便可拿到数据

2.b页面给a页面发送数据,b因为不能修改不一样域父窗口的URL,因此b页面须要动态建立一个和父窗口同域的iframe来作代理.

  • b页面建立一个src为a.abc.com/proxy.html#data的子窗口
  • 这个proxy页面经过onhashchange(兼容状况)事件监听本身href的变化,事件触发后经过修改a页面的hash来达到传递数据的功能
  • a页面访问本身的location.hash便可拿到数据

postMessage

不论是iframe和location.hash、document.domain仍是window.name都是属于非官方的跨越方法,下面要介绍的就是一个官方方法---postMessage,它是HTML5新增的一个跨文档通讯API,它实现了即便不一样域也能够跨窗口直接通讯的功能,并且只要使用得当,这种方法就很安全。

调用对象为父窗口或者的window对象、window.open()的返回值或者是iframe的contentWindow这个属性,这个方法接受两个参数,第一个是要发送的消息,第二个参数是指定接受消息的接收源,能够是*表示全部窗口均可以接收到消息或者是一个url,但只有在协议,域名和端口号都相同才会接收到消息。

添加如下代码便可接收

window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
  // For Chrome, the origin property is in the event.originalEvent
  // object.
  var origin = event.origin || event.originalEvent.origin; 
  if (origin !== "http://example.org:8080")
    return;

  // ...
}
复制代码

事件event对象有三个属性

  • data,发送过来的信息
  • origin,发送发窗口的origin
  • source,对发送消息的窗口对象的引用; 您可使用此来在具备不一样origin的两个窗口之间创建双向通讯

具体实例看这里,阮一峰老师的点击这里

参考资料

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

http://www.ruanyifeng.com/blog/2016/04/cors.html

http://blog.csdn.net/kongjiea/article/details/44201021

《JavsScript高级程序设计(第三版)》

相关文章
相关标签/搜索