跨域、CORS、JSONP

跨域关键知识

  • 同源策略

浏览器故意设计的一个功能限制

  • CORS

突破浏览器限制的一个方法

  • JSONP

IE 时代的妥协

同源策略

不同源的页面之间,不准互相访问数据

同源定义

  • window.originlocation.origin可以得到当前源
  • 源 = 协议 + 域名 + 端口号

如果两个 url 的

  • 协议
  • 域名
  • 端口号
  • 完全一致,那么这两个 url 就是同源的

举例

  • https://qq.comhttps://www.baidu.com不同源
  • https://baidu.comhttps://www.baidu.com不同源

    完全一致才算同源

同源策略定义

浏览器规定

  • 如果 JS 运行在源 A 里,那么就只能获取源 A 的数据
  • 不能获取源 B 的数据,即不允许跨域

举例(省略 http 协议)

  • 假设frank.com/index.html引用了cdn.com/1.js
  • 那么就说1.js运行在源frank.com
  • 注意这跟cdn.com没有关系,虽然1.js从它那下载
  • 所以1.js就只能获取frank.com的数据
  • 不能获取1.frank.com或者qq.com的数据

    这是浏览器的功能,浏览器故意要这样设计的

如果没有同源策略

以 QQ 空间为例

  • 源为https://user.qzone.qq.com
  • 假设,当前用户已经登录(用 Cookie)
  • 假设,AJAX 请求/friends.json可获取用户好友列表

    到目前为止都很正常

黑客来了

  • 假设你的好朋友分享https://qzone-qq.com给你

    实际上这个一个钓鱼网站

  • 你点开这个网页,这个网页也请求你的好友列表https://user.qzone.qq.com/friends.json
  • 请问,你的好友列表是不是就被黑客偷偷偷走了?

    好像是….. :(

问题的根源

无法区分发送者

  • QQ 空间页面里的 JS 和黑客网页里的 JS
  • 发的请求几乎没有区别(referer 有区别)

    Referer - HTTP | MDN

  • 如果后台开发者没有检查 referer,那么就完全没有区别
  • 所以,没有同源策略,任何页面都能偷 QQ 空间的数据

    甚至支付宝余额!

那检查 referer 不就好了?

  • 安全原则: 安全链条的强度取决于最弱一环

    万一这个网站的后端开发工程师不太彳亍呢?

  • 所以浏览器应该主动预防这种偷数据的行为
  • 总之,浏览器为了用户隐私,设置了严格的同源策略

演示一下

我们需要做两个网站来演示一下

完整代码

创建目录

  • qq-com里面有一个server.js,用来模拟 QQ 空间
  • frank-com里面有一个server.js,用来模拟坏人网站

qq-com

  • /index.html是首页
  • /qq.js是 JS 脚本文件
  • /friends.json是模拟的好友数据
  • 端口监听为 8888,访问http://127.0.0.1:8888

frank-com

  • /index.html是首页
  • /frank.js是 JS 脚本文件
  • 端口监听为 9999,访问http://127.0.0.1:9999

设置本地域名映射

  • qq.com映射到127.0.0.1,就可以访问http://qq.com:8888/index.html
  • frank.com映射到127.0.0.1,就可以访问http://frank.com:9999/index.html

    Q: 如何用 mac 设置 hosts?

    A: sudo vim /etc/hosts, 然后输入你电脑的密码进入hosts文件。按 i 键进入编辑状态,修改host。最后,ESC退出编辑状态,输入:wq 保存并退出vim

记得做完之后,删掉 hosts 里的两行,否则 qq.com 无法正常访问!

跨域 AJAX

  • 正常使用 AJAX:在qq.com里运行的 JS 可以访问/friends.json
  • 黑客偷数据: 在frank.com:9999里运行的 JS 不能访问

    浏览器需要 CORS

Q: 黑客的请求发成功了没有?

A: 成功了,因为qq.com后台有 log

Q: 黑客拿到响应了没有?

A: 没有,因为浏览器不给它数据

Q: 就没有浏览器不限制跨域么?

A: 如果不限制,就是浏览器 bug 了,快向浏览器反馈

其他疑问

为什么a.qq.com访问qq.com也算跨域?

因为历史上,出现过不同公司共用域名, a.qq.comqq.com不一定是同一个网站,浏览器谨慎起见,认为这是不同的源

为什么不同端口也算跨域?

原因同上,一个端口一个公司。记住安全链条的强度取决于最弱一环,任何安全相关的问题都要谨慎对待

为什么两个网站的 IP 是一样的,也算跨域?

原因同上,IP 可以共用,但是不同端口号可能代表不同公司

为什么可以跨域使用 CSS、JS 和图片等?

同源策略限制的是数据访问,我们引用 CSS、JS 和图片的时候,其实并不知道其内容,我们只是在引用。 不信我问你,你能知道 CSS 的第一个字符是什么吗?

跨域解法一:CORS–我就要跨域

Cross-Origin Resource Sharing (CORS) - HTTP | MDN

问题根源

  • 浏览器默认不同源之间不能互相访问数据
  • 但是qq.comfrank.com其实都是我的网站,我就是想要两个网站互相访问,浏览器为什么阻止?

好吧,用 CORS

  • 浏览器说,如果要共享数据,需要提前声明
  • 如何声明呢?浏览器说,qq.com在响应头里写frank.com可以访问
  • 具体语法
response.setHeader("Access-Control-Allow-Origin", "http://frank.com:9999");

是的,CORS 就这么简单:)

注意: CORS 分为简单请求和复杂请求,具体看文档

跨域解法二:JSONP–IE 就要跨域

定义

  • JSONP 和 JSON 半毛钱关系都没有

    由于历史问题,错误地将其称为 JSONP

我们现在面临的问题

  • IE 不支持 CORS,那怎么跨域?

    记不记得我们可以随意引用 JS?虽然我们不能访问qq.com:8888/friends.json,但是我们能引用qq.com:8888/friends.js

这有什么用? JS 又不是数据….

  • 我们让 JS 包含数据不就好了…

步骤

frank.com访问qq.com

  • qq.com将数据写到/friends.js
  • frank.comscript标签引用/friends.js
  • /friends.js执行,执行什么呢?
  • frank.com事先定义好window.xxx函数
  • /friends.js执行window.xxx({friends:[...]})
  • 然后frank.com就通过window.xxx获取到数据了

    Q: 你这个方法很聪明,但是,xxx能不写死吗?

    A: 其实名字不重要,只要frank.com定义的函数名和qq.com/friends.js执行的函数名是同一个即可,那就把名字传给/friends.js

再优化一下,封装成jsonp('url').then(f1,f2),具体看完整代码

总结一下: JSONP 是什么?

  • 我们在跨域的时候,由于当前浏览器不支持 CORS,或者因为某些原因不支持 CORS,我们必须使用另外一种方式来跨域。
  • 于是我们就请求一个 JS 文件,这个 JS 文件会执行一个回调,回调里面就有我们的数据。

    Q: 请问你这个回调的名字是什么?

    A: 回调的名字是可以随机生成的一个随机数,我们把这个名字以 callback 的参数传给后台,后台会把这个函数再次返回给我们并执行。

JSONP 就是当前网站创建一个 script 标签去请求另外一个网站的 JS,JS 里面会夹带我要的数据,而且这个 JS 会调用我写的一个全局函数来运行,就可以把数据给我了。

优点

  • 兼容 IE
  • 它可以跨域

    当然可以跨域,因为 JSONP 出现的目的不就是为了解决 IE 不支持 CORS 的问题?

缺点

  • 由于 JSONP 是 script 标签,所以它读不到 AJAX 那么精确的状态,比如状态码和响应头等,它只知道响应成功还是失败了
  • 不支持 POST

    由于它是 script 标签,所以它只能发 GET。script 不支持 POST,所以它不支持 POST:(

comments powered by Disqus