同源策略简述

同源策略是浏览器的一项基本安全功能,同源指的是协议、域名、端口号三者相同,前后端之间必须满足同源策略才能互相访问。跨域访问指前端访问与其不同源的后端,因此通常是被浏览器禁止的。

通常错误长这个样子

同源策略的好处有很多,举个最简单的例子,你随便找个网站按ctrl + s,就能把那个网站的所有前端代码保存到本地。如果不是有跨域限制,这些前端代码就依然能正常访问其服务器,这会带来极大的安全隐患。比如我复制一个淘宝下来,放到山寨服务器上,大部分链接正常访问淘宝服务器,而我偷偷修改了其付款链接,付款直接打到我账户,想想是不是很刺激? 我曾经在咸鱼上买镜头时,差点就被一个山寨的咸鱼网站给骗了,幸好我付款前感觉不对劲,打开控制台看了下它代码,发现它不是真的闲鱼…

这是维基百科上的一张表格,能比较全面的感受同源策略的限制:

  

随着现在前后端分离的项目越来越多,需要跨域访问的场景也越来越多。直到今日,这行指令我一直存在指令备忘录里:

open -a "Google Chrome" --args --disable-web-security  --user-data-dir

以允许跨域的方式打开浏览器,它成功帮助我调试了多个跨域项目~

在需要上线的项目中,肯定会出于实际的场景,选择合适的方案。具体的解决方案网上有很多,这里就不再赘述了。但是我最近在部署服务器的时候,重新注意到了这个问题,才发现这个问题和我之前理解的不大一致。

我的问题

简单来说,我有两个网站,它们的顶级域名相同,都是“xiaomaidong.com”,二级域不同,端口相同(默认的80),其他设置都是默认的,他们却可以跨域访问?这不符合图1中的规范。

在网上搜索“二级域名、跨域”等关键字可知,如果二级域名间想要跨域,需要修改domain为顶级域,比如我这里就该改成xiaomaidong.com。但是,我没改过这个。我一开始部署好两个网站,发现他们能正常访问,以为是因为我在后端(Flask框架)开启了跨域,但当我部署第二台服务器的时候,因为想指明端口,却发现后端不允许跨域,我才意识到原来我后端允许跨域的代码未生效。但是,当我用nginx做了反向代理,端口号都是80之后,就可以正常访问了。

有兴趣的同学可以访问我的这两个网站亲自确认一下: app.xiaomaidong.com qq.xiaomaidong.com

打开浏览器控制台,输入document.domain,可以发现两个网站的domain是不一样的。

探究过程

一开始,我在一些主流浏览器上(Chrome, Firefox, Safari,以及一众手机浏览器)做了测试,确定都是可以的。

因为上述两个二级域名都是通过nginx做了反向代理部署在同一台服务器上的,所以我的一个想法是,这会不会与ip有关,因为访问的是同一个ip,所以浏览器放行了?于是我做了简单的验证。我用了4个子域名,A和B在一台服务器上,C和D在一台服务器上,我尝试在D上跨域访问A,而结果也是成功的…这说明和ip是没有关系的,所以还是和域名有关。

在同学的提醒下,我又把目光放回浏览器上。同源策略是1995年才被Netscape公司引入浏览器,当时网站的复杂程度远不如现在,现在一个网站可能拥有成千上万的二级域名,比如淘宝每个商家都可以自己设置二级域名。所以,很有可能早期的浏览器严格执行同源策略,默认每一级域名都匹配才能访问,而随着技术的发展,逐渐放宽了该限制,使得只要顶级域匹配就可以访问。为了证实这个想法,我只要找到一个比较老的浏览器就行,那当然就是IE8啦。

甚至可以选择ie5

于是我在我另一个二级域名的服务器上增加了一个脚本,该脚本只是简单的利用jquery发起一次ajax请求,请求指向我服务器的后端。然后在windows虚拟机上打开IE,现在IE浏览器都支持以IE各个版本的兼容模式浏览,这点挺好的。当我在IE11、IE10上测试时,都能正确获得数据,而在IE9上测试时,控制台“网络”里看不到该请求…也就是说该请求没有产生,也没有提醒我是不是因为同源策略被禁止了。后来逐渐排除一些bug,比如一开始是因为jquery版本太高了,导致ie9不认识…最后,选择直接在console里发送该请求,终于得到了想要的结果:

注意其readyState状态

Ajax有5种状态(我还是大二的时候写过原始的ajax了…),如果readyState为4,表示成功,而readyState为0,有以下几种情况(感谢博客关于jQuery ajax 状态码status为0):

(1). url不存在

(2). url不可到达

(3). 发送了跨域请求

(4). 数据格式出错

(5). ajax在调用之前,就已经取消了。也就是说根本没有调用这个ajax请求。

(1)、(2)、(4)、(5)之前都测试过了没啥问题,所以问题只能是3。

这就说明,在IE9及以下版本的浏览器中,默认不支持二级域名的跨域,而现在主流的浏览器,基本都支持了。

至此,算是搞明白了。

一些想法

当初学计网的时候,觉得这些网络协议都设计得非常精妙,所以蛮喜欢这门课的。其实一开始我不大理解它们为什么称为协议(Protocol),而不是“算法”或“技术”?当然是上课后才慢慢理解了。我的理解是,协议是一种比较宽泛的东西,它只是做了一个大致的约定,比如两台机器间通信要符合哪些规范。而具体如何去实现这些协议,才是算法和技术考虑的事情。比如TCP协议是想要做到两台机器之间数据的可靠传输,而如何保证可靠性,这就涉及一大堆具体的算法和技术。并且,由于计算机网络发展得非常迅速,这些算法和技术还在不断更新换代,与时俱进。

所以,对于同样的协议,不同的组织,在不同的时间,完全可能开发出不同的产品,并且由于计网协议往往定义得比较宽松,对于未涉及到的细节,只要不违背协议,都是可以的。比如Http协议规定了http头部有哪些字段,但是并没有规定这些字段的名称是不是都要小写或都要大写或首字母大写…(这个坑我真踩过,见这篇博客httpt头部字段到底要大写还是小写?

附:解决跨域的一些经验

  • Flask_restful 真是个好框架,两三行代码就能搭建好restful服务器,具体参考快速搭建RESTful服务
  • 因为跨域不会限制静态资源文件,因此可以把一些不重要的数据放js文件里,这样的好处是连搭服务器都省了,只要有html文件就行了。具体参考我的这篇博客自己动手搭建照片墙

点击量:1324


发表评论

电子邮件地址不会被公开。 必填项已用*标注