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在两个网页间传递数据

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏友弟技术工作室

beego配置

beego 目前支持 INI、XML、JSON、YAML 格式的配置文件解析,但是默认采用了 INI 格式解析,用户可以通过简单的配置就可以获得很大的灵活性。 ...

6247
来自专栏me的随笔

同源策略与CORS

不同源下,浏览器不允许js操作Cookie、LocalStorage、DOM等数据或页面元素,也不允许发送ajax请求,同源下则不受影响。

1082
来自专栏散尽浮华

python升级后带来的几个小问题

1)python升级带来的yum异常:File "/usr/bin/yum", line 30 原因:这是因为yum采用Python作为命令解释器,这可以从/...

2685
来自专栏蓝天

redis for lack of backlog

版本: redis-3.2.9 部署: 5台64G内存的物理机,每台机器启动2个redis进程组成5主5备集群,每台机器1个主1个备,并且错开互备...

1215
来自专栏IT 指南者专栏

今天花时间最多的两个小错误

微信公众号:compassblog 欢迎关注、转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1、配置Maven项目的pom.xml文件报错 (1)、...

2917
来自专栏云计算教程系列

在Ubuntu 16.04如何使用Percona将MySQL类别的数据库备份到指定的对象存储上呢?

数据库通常会在您的基础架构中存储一些最有价值的信息。因此,在发生事故或硬件故障时,必须具有可靠的备份以防止数据丢失。

1503
来自专栏武军超python专栏

2018年7月31日学习ubuntu的基础操作命令和打包压缩解压缩

今天遇到的新单词: faith n 信用,信任 usage n 用法 upload n上传

1644
来自专栏我的博客

php+ftp

$ftp_server = “122.207.221.101”;//主机ip或者域名 $conn_id = ftp_connect($ftp_server) o...

3625
来自专栏张戈的专栏

解决网站404页面返回200状态码问题

好久没打理博客,突然收到 CDN 流量预警,发现平均每天 40G 流量消耗!what?就现在这个访问量,不存在的。看了下 CDN 日志发现有小人一直在请求博客页...

3143
来自专栏me的随笔

同源策略与CORS

不同源下,浏览器不允许js操作Cookie、LocalStorage、DOM等数据或页面元素,也不允许发送ajax请求,同源下则不受影响。

2234

扫码关注云+社区

领取腾讯云代金券