灵魂拷问:浏览器为什么禁止跨域

前言

跨域问题是个老生常谈的问题了,当一个页面访问非同域(域名、协议、端口号不同的都是非同域)的接口,就会由于浏览器的同源策略限制被拒绝访问。

那么问题来了,好端端的浏览器,为什么禁止我访问跨域接口呢,这个问题在知乎上也有很激烈的讨论:https://www.zhihu.com/question/26379635/answer/534866558,但是说实话,答案并不能让我满意。

经过对各种回答的反复研究,发现大家的答案都集中在两个方面:

  1. 保护服务端接口不被第三方网站恶意调用
  2. 防止 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
2
3
4
5
6
7
<button click="fetchUserInfo()">帅哥来点我啊</button>
<iframe
id="iframe1"
width="0"
height="0"
src="http://www.a.com"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fetchUserInfo(){
// 调用网站 A 的请求
fetch("http://www.a.com/api/login/getUserInfo", {
credentials: "include",
method: "GET",
mode: "cors",
})
.then((response) => {
return response.json();
})
.then((res) => {
sendInfoToWebsiteB() // 将获取到的用户信息发给恶意网站B
});
};

此时网站上只显示了一个按钮:

假如张三手痒点了这个按钮的话,就触发了页面的 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 的白名单中,可以看出,浏览器对于跨域的行为还是很敏感的。