跨域、CORS、JSONP
跨域关键知识
- 同源策略
浏览器故意设计的一个功能限制
- CORS
突破浏览器限制的一个方法
- JSONP
IE 时代的妥协
同源策略
不同源的页面之间,不准互相访问数据
同源定义
源
window.origin
或location.origin
可以得到当前源- 源 = 协议 + 域名 + 端口号
如果两个 url 的
- 协议
- 域名
- 端口号
- 完全一致,那么这两个 url 就是同源的
举例
https://qq.com
、https://www.baidu.com
不同源https://baidu.com
、https://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,那么就完全没有区别
- 所以,没有同源策略,任何页面都能偷 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.com
和qq.com
不一定是同一个网站,浏览器谨慎起见,认为这是不同的源
为什么不同端口也算跨域?
原因同上,一个端口一个公司。记住安全链条的强度取决于最弱一环,任何安全相关的问题都要谨慎对待
为什么两个网站的 IP 是一样的,也算跨域?
原因同上,IP 可以共用,但是不同端口号可能代表不同公司
为什么可以跨域使用 CSS、JS 和图片等?
同源策略限制的是数据访问,我们引用 CSS、JS 和图片的时候,其实并不知道其内容,我们只是在引用。 不信我问你,你能知道 CSS 的第一个字符是什么吗?
跨域解法一:CORS–我就要跨域
问题根源
- 浏览器默认不同源之间不能互相访问数据
- 但是
qq.com
和frank.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.com
用script
标签引用/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:(