JSONP && CORS

  前天面试被问到了跨域的问题,自我感觉回答的并不理想,下面我就分享一下整理后的总结分享给大家

一、为什么要跨域

  安全限制

  JavaScript或Cookie只能访问同域下的内容——同源策略

  同源策略

  下表相对于: http://h5.jd.com/dir/ajax.js

  

  注意

  • 协议和端口造成的跨域问题,非前端解决范畴
  • 所谓域,是通过“url首部”来识别,而非判断域与ip的对应关系

  (“URL的首部”指window.location.protocol +window.location.host)

二、跨域方案

1. jsonp

  原理

  HTML里面所有带src属性的标签都可以跨域,如iframe,img,script等。

  所以可以把需要跨域的请求改成用script脚本加载即可,服务器返回执行字符串,但是这个字符串是在window全局作用域下执行的,你需要把他返回到你的代码的作用域内,这里就需要临时创建一个全局的回调函数,并把到传到后台,最后再整合实际要请求的数组,返回给前端,让浏览器直接调用,用回调的形式回到你的原代码流程中。

  • 首先,利用 script 标签的 src 属性实现跨域
  • 通过将前端方法作为参数传递到服务器端,然后由服务器注入参数之后再返回,实现服务器端向客户端通信
  • 由于使用script 标签的src 属性,因此只支持 get 方法

  一个简单的jsonp实现,其实就是拼接url,然后将动态添加一个script元素到头部

1. 设定一个script标签
<script src="http://jsonp.js?callback=xxx"></script>
2. 服务器
$callback = !empty($_GET['callback'] ? $_GET['callback'] : 'callback');
echo $callback.'(.json_encode($data).)';

 

  详见博客 JSON 和 JSONP两兄弟

2. cors

方案对比
  JSONP CORS
目的 跨域 跨域
支持

get

(受IE下url长度不能超过2083个字节的限制和出于安全考虑,一般不用来提交数据)

所有类型的http请求
支持度 包括老式浏览器 不支持部分浏览器,移动端支持很好
缺点

1)安全问题(请求代码中可能存在安全隐患)

2)确定jsonp请求是否失败不太容易

3)只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题

支持率
原理

被包含在一个回调函数中的JSON

核心则是动态添加<script>标签来调用服务器提供的js脚本

(允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了)

使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败

(只需由服务器发送一个响应标头即可)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CORS需要浏览器和服务器同时支持

实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨域通信


1)两种请求方式

  简单请求、非简单请求

  a)简单请求:

  

  

  跨域时,浏览器自动在头部信息中添加一个origin 字段(指定请求源-协议+域名+端口),如下图所示

  

  服务器判断origin在域名许可范围内,返回响应:

  

  若不存在 Access-Control-Allow-Origin 字段,则出错

  以上头部信息中,CORS相关字段有

  • Access-Control-Allow-Origin
    必须字段,其值为 origin / *(可接受任意域名请求)
  • Access-Control-Allow-Credentials
    可选,是否允许发送Cookie
  • Access-Control-Expose-Headers
    可选,是否需要Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma之外的字段

  withCredentials 属性

   CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段

Access-Control-Allow-Credentials: true

 

  另一方面,开发者必须在AJAX请求中打开withCredentials属性。

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

 

  注意

  如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

  b)非简单请求(不同时满足以上条件)

  请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

   浏览器对于非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息

  

  

  除了Origin字段,"预检"请求的头信息包括两个特殊字段。

  • Access-Control-Request-Method

   该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT

  • Access-Control-Request-Headers

   该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

 

2.CORS 支持度

 

 

3. document.domain

两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie

document.domain = 'example.com';

 

现在,A网页通过脚本设置一个 Cookie

document.cookie = "test1=hello";

 

B网页就可以读到这个 Cookie。

var allCookie = document.cookie;

 

注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。

 

4. iframe

 (只有在主域相同时才能使用)

  1)www.a.com/a.html中:

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    //在这里操作doc,也就是b.html
    ifr.onload = null;
};

 

  2) 在www.script.a.com/b.html中:

document.domain = 'a.com';

 

5. window.postMessage:

该方法是 HTML5 新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

window.postMessage的功能是允许程序员跨域在两个窗口/frames间发送数据信息。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个客户端之间通信

 

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!', '*');
}

 

event.origin属性可以过滤不是发给本窗口的消息。

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
    if (event.origin !== 'http://aaa.com') return;
    if (event.data === 'Hello World') {
        event.source.postMessage('Hello', event.origin);
    } else {
        console.log(event.data);
    }
}

 

 

 

【a.qq.com页面代码】

 

 【b.qq.com页面代码】

以上demo简单解决了前端跨域通信,跨域带cookie等问题,在逻辑上完全可以实现跨域通信。但是对于不支持PostMessage特性的老版浏览器是行不通的。比如IE8-浏览器就不能很好的支持PostMessage特性

6. 服务端代理

在数据提供方没有提供对JSONP协议或者window.name协议的支持,也没有对其它域开放访问权限时,我们可以通过server proxy的方式来抓取数据。

例如当www.a.com域下的页面需要请求www.b.com下的资源文件asset.txt时,直接发送一个指向 www.b.com/asset.txt的ajax请求肯定是会被浏览器阻止。

这时,我们在www.a.com下配一个代理,然后把ajax请求绑定到这个代理路径下,例如www.a.com/proxy/, 然后这个代理发送HTTP请求访问www.b.com下的asset.txt,跨域的HTTP请求是在服务器端进行的,客户端并没有产生跨域的ajax请求。

这个跨域方式不需要和目标资源签订协议,带有侵略性,另外需要注意的是实践中应该对这个代理实施一定程度的保护,比如限制他人使用或者使用频率。

其他跨域方案

window.name:

在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。

动态创建script

JSONP也就是利用这个原理。

利用iframe和location.hash

淘汰类技术

利用flash

淘汰类技术

 

参考链接:

http://tech.jandou.com/cross-domain.html

http://www.cnblogs.com/JChen666/p/3399951.html

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

用HTML5里的window.postMessage在两个网页间传递数据

 

 

 

 

 

 

 

 

posted @ 2017-03-19 10:25  超然haha  阅读(546)  评论(1编辑  收藏  举报
back to top