前言
跨域问题是个老生常谈的问题了,当一个页面访问非同域(域名、协议、端口号不同的都是非同域)的接口,就会由于浏览器的同源策略限制被拒绝访问。
那么问题来了,好端端的浏览器,为什么禁止我访问跨域接口呢,这个问题在知乎上也有很激烈的讨论:https://www.zhihu.com/question/26379635/answer/534866558,但是说实话,答案并不能让我满意。
经过对各种回答的反复研究,发现大家的答案都集中在两个方面:
- 保护服务端接口不被第三方网站恶意调用
- 防止 XSRF 攻击
那么接下来,我们针对这两点进行解释。
保护服务端接口不被第三方网站恶意调用
这个其实很好理解,我们辛辛苦苦写出一个接口,并且将其部署到服务器上,肯定不想让别人直接调用我们的接口,盗取我们头发的代价不说,还占用我们的服务器资源。
举个例子:
假如我创建了一个山寨搜索引擎“百毒”,假如浏览器没有禁止“百毒”网站的跨域行为,用户在我的网站进行搜索后,我就在我的页面上偷偷调用百度的搜索接口,然后再把结果返回给用户。好在浏览器禁止了这一行为。
但是有的小聪明就要问了,我拿到不可以把请求在服务器端进行处理,然后再转发给用户吗?答案是肯定的,你甚至可以利用爬虫的技术来获取各种各样的资源,但是这一行为必然是违法的,而且一但百度发现了你服务器的异常请求行为,就会将你的服务器ip给拉黑,想盗取也没有办法。
防止 XSRF 攻击
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。[1] 跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
以下配一张图来简单介绍一下 XSRF 攻击盗用用户登录凭证,来窃取用户在第三方网站信息的流程:
我们先来假设张三正在使用一个不合格的浏览器,这个浏览器没有任何的跨域限制。
当张三访问了网站A,假如网站A使用了 Cookie 用来传递用户的登录凭证,那么在张三登录成功之后,网站A会向张三的浏览器用写入 cookie,该 cookie 的作用域名为 http://www.a.com
。
之后张三访问了恶意网站网站B,恶意网站呢网站B偷偷的在网页里嵌入一个网站A的 iframe,其代码如下:
1 | <button click="fetchUserInfo()">帅哥来点我啊</button> |
1 | function fetchUserInfo(){ |
此时网站上只显示了一个按钮:
假如张三手痒点了这个按钮的话,就触发了页面的 fetchUserInfo
方法,会直接发送一个网站A的请求,加上张三是用的浏览器没有跨域限制,跨域请求将会被成功发送。重点来了,恶意网站网页B嵌入了网页A的 iframe 后,恶意网站B发送的请求,只要请求域名在网站A的 cookie 的作用范围之内,就可以携带网站A的 cookie。
之所以网站B会存在网站A的 cookie,是因为存在
第三方cookie
这种机制,在这种机制下,同一个网页下可以有多个域名的 cookie,如下:这是一种没有办法完全禁止的行为,因为很多广告商需要第三方 cookie 来实现用户追踪这,这个可以另写一篇文章具体讲。
好在我们日常生活中的浏览器都是默认禁止跨域的,我们就大可不必恐慌于这种攻击行为了。
那又有聪明的同学要问了,假如服务器端的开发人员很粗心,Access-Control-Allow-Origin 填成了 *
,这个接口能被第三方网站请求到的话,岂不是很危险?其实这里还有一个限制,假如请求需要携带 cookie,也就是我们在 fetch api 中设置了 credentials: "include"
,那么在请求 Access-Control-Allow-Origin: *
的接口时就会报错:
这个意思是,如果我们允许在请求头中传递 cookie 的话,响应报文的 Access-Control-Allow-Origin
就不能是通配符,因此如果想要允许网站在发送跨域请求携带 cookie 时候,跨域请求所在的服务器必须要将该网站设置在 Access-Control-Allow-Origin
的白名单中,可以看出,浏览器对于跨域的行为还是很敏感的。