同源策略
同源
源
window.origin 或 location.origin 可以得到当前源
源 = 协议 + 域名 + 端口号
同源定义
如果两个url的协议、域名、端口号完全一致,那么这两个url就是同源的
举例——
baidu.com、 http://www,baidu.com 不同源
必须完全一致才同源
同源策略
浏览器规定
如果JS运行在源A里,那么只能获取源A的数据,不能获取源B的数据,即不允许跨域
举例(省略http协议)
假设 river.com/index.html 引用了 cdn.com/1.js,那么就说"1.js运行在源 river.com 里"
注意这跟 cdn.com 没有关系,虽然1.js从它那下载
所以1.js就只能获取 river.com 的数据,不能获取 1.river.com 或者 qq.com 的数据
这是浏览器的功能
为了保护用户隐私,浏览器故意这样设计的
如果没有同源策略
以QQ空间为例
假设当前用户已经登录(用Cookie),假设AJAX请求 /friends.json可获取用户好友列表
黑客攻击
如果有人分享了 qzone-qq.com 的钓鱼网站
点开这个网页,这个网页请求用户好友列表
user.qzone.qq.com/friends.jso…
好友列表就被黑客拿到了
问题的根源
无法区分发送者
QQ空间页面里的JS和黑客网页里的JS,发送请求几乎没有区别(referer有区别)
如果后台开发者没有检查referer,那么就完全没区别
所以,没有同源策略,任何页面都能偷QQ空间的数据
检查referer?
安全原则:安全链条的强度取决于最弱一环
所以浏览器应该主动预防这种行为,总之浏览器为了用户隐私,设置了严格的同源策略
举个例子
步骤
创建目录
qq.com里面有一个server.js,用来模拟QQ空间
baidu.com里面有一个server.js,用来模拟黑客网站
qq.com
/index.html是首页
/qq.js是JS脚本文件
/friends.json是模拟的好友数据
端口监听为8888,访问http://127.0.0.1:8888
baidu.com
/index.html是首页
/frank.js是JS脚本文件
端口监听为9999,访问http://127.0.0.1:9999
设置本地域名映射
让qq.com映射到127.0.0.1,直接访问qq.com:8888/index.html
让baidu.com映射到127.0.0.1,直接访问baidu.com:9999/index.html
AJAX
正常使用AJAX
在qq.com:8888里运行的JS可以访问/friends.json
黑客偷数据
在baidu.com:9999里运行的JS不能访问
浏览器需要CORS
提问
黑客的请求发送成功了,qq.com后台有log。但是黑客没有拿到响应,因为浏览器不给数据。
其他疑问
-
为什么
a.qq.com访问qq.com也算跨域?因为历史上,出现过不同公司共同域名,
a.qq.com和qq.com不一定是同一个网站,浏览器谨慎起见,认为这是不同的源。 -
为什么不同端口也算跨域?
原因同上,一个端口一个公司。
-
为什么两个网站的IP一样,也算跨域?
原因同上,IP可以共用。
-
为什么可以跨域使用CSS、JS和图片?
同源策略限制的是数据访问,我们引用CSS、JS和图片的时候,其实并不知道其内容,只是在引用。
CORS
实现跨域的方法
问题根源
浏览器默认不同源之间不能互相访问数据。
如果qq.com和baidu.com其实都是我的网站,我想要两个网站互相访问,浏览器也提供了共享数据的方法。
用CORS
需要提前声明,qq.com在响应头里写baidu.com可以访问。
JSONP
IE 6 7 8 9 都不支持CORS,若要兼容IE,可以使用JSONP。
定义
JSONP和JSON没有关系
步骤
qq.com将数据写到/friends.js
baidu.com用script标签引用/friends.js
/friends.js执行
baidu.com事先定义好window.xxx函数
/friends.js执行window.xxx({friends:[...]})
然后frank.com通过window.xxx获取到数据了
优化
-
window.xxx可以改其他名字,只要baidu.com定义的函数名和qq.com/friends.js执行的函数名是同一个即可 -
封装
封装成
jsonp('url').then(f1,f2)
JSONP是什么?
在跨域的时候,当前浏览器不支持CORS,就可以采用JSONP。
具体操作就是创建一个script请求JS文件,这个JS文件执行回调,回调里面就有需要的数据。
JSONP优点可以支持跨域,缺点由于是script标签,所以做不到AJAX那么精确的状态,只能发get请求,不支持post。