跨域

作者: 水剑承王 | 来源:发表于2017-08-23 22:32 被阅读0次

这篇文章我们来聊聊「跨域」,这个概念在很多公司的面试中会被问到,网上也有数不清的关于「跨域」的文章。

那我为什么还要费神去写一篇?

因为我发现很多文章都是互相抄袭,写了一大堆你也没有搞懂他到底在讲什么,基本上技术文章都有这个通病,不知道是作者觉很多知识是不言自明的,所以不屑于讲;还是说其实他自己也没搞清楚,把别人的文章搬过来,随便改改,就成自己的了。

废话不多说了,下面进入正题。

往往我们要解释清楚一个概念,必须引入另一个概念,「跨域」也是如此。

要搞清楚「跨域」,先要搞清楚什么是「同源策略」(Same-origin policy),为了搞清楚「同源策略」,我们先讲一些其他的东西。

我们写任何程序,本质上只做两件事件:计算数据和存储数据,CPU 负责计算,内存负责存储。计算机之所以牛逼,不是因为它能做的事情多,而是因为它计算的速度相当快,能存储的数据非常多。
既然计算和存储都由计算机搞定了,那数据呢,数据从哪来呢?
数据的来源有很多种,比如你可以在程序里自己造数据,也称为字面量(literal)数据;也可以从本机磁盘里读取文件数据;可以从内存中读取其他程序或资源的数据;还可以从网络服务器上请求数据,等等。
但是对于前端开发来说,我们使用 JavaScript 语言来写程序,一般是操作网页,获取数据的途径一般就三种:自己造数据;读取当前页面的数据;从网络服务器上请求数据。

  • 第一种没什么好说的,就是写一堆字面量:const greeting = 'hello world'
  • 第二种也简单,比如获取 DOM 元素的数据:const lists = document.querySelectorAll('li'),或者获取各种 Storage(Local Storage, Session Storage, IndexedDB, Cookies 等) 里面存的数据:localStorage.getItem('person_key')
    • 这里存在一种情况,就是在当前页面里面有 iframe 元素时,iframe 里面本质上是嵌入了另一个页面
    • 这个页面有它自己的一个全局环境(window 对象),所以它也有自己完整的一套 DOM,Storage
    • 所以这时如果要从当前页面读取这个嵌入页面的数据的话,也会存在是否同源的问题,如果不同源的话,也需要跨域
    • 但是因为 iframe 元素用的不多,所以我们这里先不做考虑
  • 第三种就麻烦一点了,我们一般是发送 Ajax 请求来获取服务器端的数据

我们重点讨论第三种方式。

要发送 Ajax 请求,就必然要指定一个 URL,这样才能告诉浏览器的 Ajax 引擎去哪获取数据。

先说一个概念,我们写的一段 JavaScript 程序,不管你是用原生 JavaScript 和最原始的方式写(script 标签),还是用各种库、框架、构建工具写,最终都必然要运行在某个页面上,这个页面也必然对应着一个它自己的 URL。

那么这时候问题就来了,如果你在 Ajax 请求中指定的这个 URL 跟你当前运行这段 JavaScript 程序的页面的 URL 不同源的话,你的这次请求很可能就会被浏览器拒绝(在没有实现跨域的情况下,一定会被拒绝),拒绝的表现就是在控制台给你报一个错,当然数据也拿不到。

问题又来了,什么叫同源,什么叫不同源?为什么不同源浏览器会拒绝呢?这就终于涉及到了我们在文章开头说的「同源策略」。

让我们先在脑中有一个基本的概念,虽然我们现在可能还不清楚同源的概念,但是既然涉及到相同和不相同,那一定要掌握两方面的信息才能得出是否相同的结论:谁跟谁做比较?比较的东西是什么?比如「你跟你爸的长相是否相同」,比较的双方就是你和你爸,比较的东西就是你们的长相。
具体到同源这个概念,用来做比较的双方分别是:

  • 在当前页面下运行的 JavaScript 程序中,发送 Ajax 请求的代码里面指定的 URL
  • 当前页面的 URL

比较什么东西呢?就是同源里的「源」(origin),要知道什么是「源」,我们先看一下 URL 的组成

我们知道,一个完整的 URL 由以下几部分组成
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

其中协议(scheme),域名(host),端口号(port)共同组成了「源」,所以是否同源就是比较它们三个。
这三者必须完全相同,两个 URL 才算是同源,只要有一个不相同,就是不同源。判断是否完全相同是按照字符逐个比较,字符没有完全匹配即是不相同。
比如域名 127.0.0.1localhost 都表示本机域名,但是因为它们的字符不同,所以也算不同源。
具体例子有很多,我就不写了,网上一大把,写这个没意思。

知道了什么是同源和不同源,我们再来说什么是「同源策略」。

首先要知道同源策略是浏览器实现的一种网页安全机制,所谓策略或者说机制,就是浏览器根据某些条件和计划来做出对应的行为,这样说有点虚。举个例子:你爸跟你说,下次考试你要是考到了全班前三名,就奖励你一个苹果手机,你要是不及格,就给你一顿揍。这就是你爸制定的一个激励你学习的策略。
具体到同源策略,就是浏览器说,你要是给我的 URL 同源,我就给你数据;要是不同源,呵呵,我就给你报错。

虽然它是一种实现(implementation),但是并不存在一个与之对应的规范(specification),这里插一句,说一下什么是规范,什么是实现。

举个例子,如果说「规范」就是一份说明怎么制造一个杯子的文件(用什么材质,什么结构,制作工序等等),那么「实现」就是根据这个规范做出来的杯子。
在 Web 里面,制定规范的都是一些非盈利性的组织和机构,比如 W3C,ECMA 等等,实现规范的就是各大浏览器厂商,比如 Chrome, IE, FireFox 等等,对某个规范的实现就是某个具体的功能,比如 HTML 规范的实现就是浏览器能正确解析 HTML 文件,将它们显示成页面的这个功能。
需要注意的是,实现并不一定完全符合规范,就好比你生产一个杯子,如果消费者喜欢有把手的杯子,但是规范上又没说要加把手,这时候你很可能会不按照规范,做一个有把手的杯子出来。
Web 里面同样如此,有些规范里有的内容浏览器没有实现,有些规范里没有的内容,浏览器又实现了。说白了规范对实现没有约束力,我觉得你好我就实现,觉得不好压根不理你。

扯远了,回到同源策略。前面说到同源策略没有规范,所以浏览器厂商各自的实现并不完全一样,但基本目的是一样的:一个页面中的 JavaScript 程序,无法访问到跟它不同源的另一个页面(或者URL)里面的数据。这里所说的数据,就是我们前面说的 DOM 元素数据,Storage 数据,还有 Ajax 请求的数据。

那么问题来了,浏览器为什么要这么做呢?

其实很好理解,它是出于网络安全的考虑。想像一下如果没有同源策略,那任何一个网页都可以通过编写 JavaScript 程序来获取其他网页的敏感数据,这不是很恐怖吗?假如你刚刚登陆了网银,然后又访问了某个恶意网页,那这个恶意网页就可以访问甚至修改你的网银账户数据。这样的话,网络就一点都不安全了。

虽然浏览器搞的这个同源策略看起来很美好,但凡事有利就有弊,有时候我们也不希望它存在。

比如我要发送 Ajax 请求访问某个 URL 来获取数据,这些数据是别人网站提供的公共数据,没有安全性问题,谁都能访问(比如一些网部的天气数据 URL 接口,新闻数据 URL 接口等等),但是因为同源策略,不好意思,浏览器直接给你拒了。
还比如我们公司是百度,baidu.com 这个主域名下有很多子域名:news.baidu.com, tieba.baidu.com 等等,那我的主域名页面要获取子域名页面的数据,还有子域名页面相互之间通信都会受到同源策略的限制。

那这要怎么办呢?这时候就终于引出了我们要说的重点:「跨域」

要解决同源策略对于不同源网页之间通信的限制问题,就要通过跨域来处理。所谓跨域就是下面我们要说的解决方案的统称,是一套技术的集合。
通过前面的学习,我们发现跨域这个词其实并不太准确,应该叫「跨源」,因为域名仅仅是源的一部分而已(其他两部分是协议名和端口号),但是我们在实际开发中遇到最多的还是域名不同导致的不同源,所以才有了跨域这个说法(仅仅是笔者的猜测)。

好了,大家可以发现,我们花了很多时间来解释概念性问题,写了这么多还没说到跨域应该怎么跨,别的技术文章到这估计都把跨域的几种方式总结完了,但是那样又有什么意义呢?你总结了十来种方式,其实工作中用到的也就那么一两种,你是人不是机器,那么多技术方案你都要背下来吗?
很多东西搞清楚 what 和 why 比 how 更重要,因为 how 是不断变化的,但是原理性的东西一般不会轻易改变。掌握了 what 和 why,再来学 how 就很轻松了。

其实写到这里,后面跨域的几种方案,都可以在网上找到详细的说明。笔者主要想详细说一下 JSONP 和 CORS 这两种常用的方法,今天太累了,明天再写。

相关文章

网友评论

      本文标题:跨域

      本文链接:https://www.haomeiwen.com/subject/ijojdxtx.html