我们之前提到过,AJAX技术使开发者能够专注于互联网中数据的传输,而不再拘泥于数据传输的载体。通过AJAX技术,我们获取数据的方式变得更加灵活,可控和优雅。
但是AJAX技术并不是一把万能钥匙,互联网中的数据隐私和数据安全(例如你的银行账号和密码)也非常重要,为了保护某些用户数据的隐私与安全,浏览器使用“同源策略”限制了AJAX技术获取数据的范围和能力。但在一些合理的场景中,我们又不得不想办法绕过同源策略,实现跨域请求资源。因此“跨域技术”一直成为开发者们经久不衰的讨论话题。
在“跨域获取资源”这一主题中,我们将围绕“同源策略”和“跨域”两大主题展开,不但讲述它们是什么,更说明了为什么要这么做。相信你在读完该主题下的两篇文章后,一定会对这两大主题有一个清晰,系统的认识。
需要提前声明的是,本主题下的文章并不会像众多相同主题的文章一样罗列出所有的跨域技术,而只会捡最主流的四种进行讲解。因为我并不打算写“教你如何跨域”这样类型的文章。
让我们开始吧。
同源策略
整个互联网世界的数据要么存储在服务端(即服务器,如数据库,硬盘等)中,要么存储在客户端(即浏览器,如cookie,LocalStorage,sessionStorage)中。互联网数据的传输实际上就是客户端与服务端之间的交互。
而所谓的数据隐私与安全保护,说白了就是数据拥有者对数据索取者发出警告:“不是你的你别动”。
搞清了这个原则,我们就很容易明白,如果你在客户端,并且想要获取服务端数据,你首先需要通过服务器端的验证,证明你有权限获取数据(例如“登录”),而如果你在服务端,想要获取客户端的某些数据,你同样需要客户端通过某些方式验证你有资格获取相应的数据资源。
那么上面提到的“某些方式”是什么呢?其中最重要的就是我们今天的主题之一 -- 浏览器的“同源策略”。
浏览器的“同源策略”
浏览器所遵守的“同源策略”是指:限制不同源之间执行特定操作。这涉及到两个问题:什么是“源”?,以及“特定操作”是指什么?
让我们停下来解释一下这个概念:
- 一个源由协议,域名和端口三部分组成,这三者任一一个不同都会被浏览器识别为不同的源;
- 上文所提到的特定操作是指:
- 读取 Cookie,LocalStorage 和 IndexDB;
- 获取 DOM 元素;
- 发送 AJAX 请求;
在搞清了同源策略的概念之后,让我们看看浏览器是出于怎样的考虑,一直坚守着同源策略:
为什么要有“源”的概念?
因为不同的源,大多数情况下就意味着它们在互联网中归属于不同的站点(或是被用作不同的用途)。也就是说它们是不同的项目,有不同的文件根目录,那么它们的数据也不应该共享也就理所应当了,否则数据的隐私和安全也无从谈起。不过请注意,我上面所说的话是基于“不同源就彼此不相干”的假设,这其实存在一些问题,我们之后会提到。
为什么不能执行“特定操作”?
这个需要我们假设,如果我们想做一些“坏事”,并且浏览器允许我们执行这些“特定操作”,我们作为“坏人”能做什么:
首先,由于很多网站使用浏览器存储用户的用户名和密码,那么我们便可以在A域中(我们在服务器上托管的网站)读取任意来访用户的所有Cookie信息(没有同源策略的保护,该用户所有网站的Cookie记录都是透明的),我们就可以利用这些Cookie信息伪装成来访用户做任何事,而在现实世界,出于同源政策的保护,我们只能访问用户该域下的Cookie信息,也就是说,我们只能访问我们自己设置的Cookie信息。
其次,如果我们能够获取不同域下的DOM元素,我们就可以通过标签在我们的A域网站上引入B域网站,然后诱使用户在B域网站操作,由于我们能够跨域获取DOM元素,因此我们可以操作B域网站的DOM结构,用户输入的一切信息,以及用户操作的DOM元素就都在我们的掌控之中了。这正是同源策略想要规避的安全隐患。
最后,为什么要禁止不同源的站点发送AJAX请求呢?这个说起来有些复杂,我们首先要对Cookie的运作原理有一个大致的了解:
当我们设置Cookie时,除了存放键值对形式的数据信息外,浏览器还会为Cookie的一些属性填充默认值(我们也可以手动修改这些属性的值)。在这些属性中,我们需要关注domain
和path
两个属性,它们一个代表域名,一个代表路径,两者加起来构成了一个确定这条Cookie何时被调用和访问的URL。与此同时,浏览器自己维护的Cookie文件中也会添加这一条新创建的Cookie数据。
当我们在浏览器中发送HTTP请求时,浏览器首先会检查请求地址并在自己所维护的Cookie文件中寻找匹配的Cookie信息,将其添加到请求头中的Cookie
属性内,然后向服务器发送请求。请注意,这个自动添加相应Cookie信息的过程是浏览器自己偷偷帮我们做到的,也就是说,我们自己无法控制这个过程。
下面重点来了,当我们的HTTP请求到达服务器时,服务器返回的响应中,响应头会原封不动的返回我们发送给他的Cookie信息。嗅到危险的味道了吗?我们虽然不能在发送请求前获得Cookie信息,但是在发送请求后,我们还是能够获得用户的Cookie!
让我再进一步解释一下这和AJAX有什么关系,假设我们在自己的服务器上托管了站点A,并在其中隐藏了一段脚本,每个登录站点A的人都会自动发送AJAX请求至站点B(提示:站点B是一个银行),那么在没有浏览器同源策略的情况下,如果站点A中的访问者恰好有Cookie中保留站点B信息的用户,通过AJAX请求返回的响应头,我们一样可以拿到这位用户的站点B Cookie,从而伪装成用户在站点B登录,做一些违法乱纪的事情(CSRF攻击即是利用了这个原理,只不过出于同源策略限制,并不能通过发起AJAX的方式)。
虽然有些费劲,但是现在你应该明白为什么同源策略要阻止跨域发送AJAX了吧?(我终于将这个概念说清楚了,真是费了不少力气 ?)
想要了解更多关于Cookie的信息,可以参考这篇文章
同源策略的表现
看看我们的任务清单,我们解释了什么是同源策略以及浏览器为什么要有同源策略,但至今为止,我们还不曾看到,在浏览器同源策略的限制下,当我们想要获取跨域Cookie,DOM结构和发送AJAX时,我们是如何被“拒绝”的。
首先,我们在一个域下只能读取该域下的Cookie值。当我们在页面中使用标签时,我们获取对应DOM节点下只有一个空空的
#document
节点,并没有额外的DOM信息。
而对于AJAX,浏览器其实并没有阻止我们向不同域发送请求,其阻止的是这次请求的响应,也就是说服务端其实接收到了这次请求,只是响应被浏览器解析时被浏览器发现违背了同源策略而被拒绝,此时,浏览器会在控制台中打印出一条错误信息。
另外需要注意的是,对于XHR请求,实际上我们在请求报头也不会看到相应的Cookie信息,这是因为CORS标准中做了规定,默认情况下,浏览器在发送跨域请求时,不能发送任何认证信息,比如cookies
和HTTP authentication schemes
。除非你显式的将xhr
实例的withCredentials
属性的值设置为true
并且服务器端也允许客户端请求携带认证信息(即服务器端在响应头中设置了Access-Control-Allow-Credentials: true
)。
要在跨域请求头中显示Cookie真是不容易对吧?
但是,等等!CORS标准是什么?
问得好,在下一篇以“跨域发送请求”为主题的文章中,我们会详细谈到他。目前为止,你已经充分了解“同源策略”这个主题。Good Job!
现在让我们先暂时休息一下,下一篇见 ?。
? Hey!喜欢这篇文章吗?别忘了在下方? 点赞让我知道。