详解浏览器的同源策略和跨域问题

1,403 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

概述

本文从以下三个问题,循序渐进,一步步拆解,何为浏览器的同源策略和跨域问题:

  1. 什么是同源策略和跨域问题?
  2. 怎样才算同源?
  3. 同源策略下会有什么限制?

什么是同源策略和跨域问题?

同源政策是 1995 年由 Netscape 公司(就是创造了 JS 的那家网景公司)引入浏览器的一种策略。

想要理解同源策略的话,可以先假设如果没有同源策略会发生什么事情。接下来我们通过一则恐怖故事代入理解一下没有同源策略的世界:

在某个深夜,小明独自一人在家浏览网页,无意间打开了一些特殊网站,而恰巧的是,小明之前刚好打开过某些重要支付平台的网站且还没关闭,这时特殊网站里面可能有一些特殊 JS 脚本,在偷偷获取小明支付平台上的信息,并且偷偷利用了这些信息再结合某些手段,成功把小明支付平台上的钱转走了,小明上完厕所回来后,突然收到支付平台转账成功的短信,然后然后然后瘫坐在地上,泣不成声,仰天长啸,大喊一声:“还我攒了半年的 9.99~”

恐怖故事听完了,我们来先上个概念,什么是同源策略?

借用 MDN 上的描述

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

怎么理解这个概念呢?举个栗子,随着汽车数量的上升,现在很多城市都有各种限行政策,就拿广州来说,如果不是 粤A 的车牌进入城区的话会有开四停四的限行限制。

同源策略跟汽车限行政策有一点点像,同源策略规定在“同源”下的脚本、文档等资源访问不受限(有点像 粤A 牌的汽车在广州内行驶不会被限行),如果不是“同源”则无法互相访问脚本、文档等资源(非 粤A 牌的汽车在广州市区内受开四停四政策限制)

理解了同源策略后,对于跨域就很好理解了,同源策略是好,我们也需要同源策略,但有些时候我们确实需要不同源的两个网页互相读取信息,这是就产生了跨域问题。

额外补充,同源策略是浏览器的行为,是浏览器的行为,是浏览器的行为(对于这点的解析可以参考:同源策略下,服务器会收到浏览器的请求吗?

怎样才算同源?

所谓“同源”,就是以下三者必须都相同

  1. 协议相同
    • 协议相同的两个域名:(都是 https 协议)
      • https://juejin.cn
      • https://juejin.cn
    • 协议不同的两个域名:(一个是 http 协议,一个是 https 协议)
      • http://juejin.cn
      • https://juejin.cn
  2. 域名相同
    • 域名相同的两个域名:
      • https://juejin.cn
      • https://juejin.cn
    • 域名不同的两个域名:(一个是二级域名,一个是三级域名)(可能这个是最容易记错的)
      • https://juejin.cn
      • https://www.juejin.cn
  3. 端口相同
    • 端口相同的两个域名:
      • https://juejin.cn
      • https://juejin.cn
    • 端口不同的两个域名:
      • https://juejin.cn
      • https://juejin.cn:8081

再重复一遍,

同协议、同域名、同端口,才算同源

同协议、同域名、同端口,才算同源

同协议、同域名、同端口,才算同源

同源策略下会有什么限制?

在同源策略下,如果非同源,会有以下三个方面的行为受到限制:

  1. DOM
    • 如果非同源,其 JS 脚本,不能互相对 DOM 进行读写操作
  2. 数据
    • 如果非同源,不能互相读写 Cookies、LocalStorage、SessionStorage 和 IndexedDB
  3. 网络请求
    • 如果非同源,不能通过 AJAX 的方式互相发送、接收数据

为了方便理解,接下来我们展开说说这三条规则 ↓↓↓

同源下,JS 可以操作 DOM

如上图,我们在谷歌浏览器上打开 https://juejin.cn/post/7123120819437322247 然后打开开发者工具,执行以下 JS 脚本

// 在另外一个窗口上打开 https://juejin.cn/ 并且返回打开的新窗口对象的引用
var www = window.open('https://juejin.cn/', '_blank');

// 然后等 https://juejin.cn/ 窗口打开后,还是在上一步的开发者工具中执行:(执行完后会发现,刚通过 window.open 打开的 https://juejin.cn/ 窗口,DOM 中的 body 标签里面的内容直接被隐藏了)
www.document.body.style.display = 'none';

// 想要让其重新展示,再执行下面脚本即可:
www.document.body.style.display = 'block';

上述例子中,虽然分别打开了 https://juejin.cn/post/7123120819437322247https://juejin.cn/ 两个窗口,但因为他们是同源的,所以 https://juejin.cn/post/7123120819437322247 中的 JS 脚本可以操作 https://juejin.cn/ 中的 DOM。

PS:上述例子都是建立在通过 window.open() 获取到 https://juejin.cn/ 窗口对象的引用(window.opener)的前提下进行的。如果只是单纯直接打开两个窗口,没有 window.opener 的话,也是不可以跨窗口操作 DOM 滴  ̄ω ̄=

同源下,才能读写 Cookies、LocalStorage、SessionStorage 和 IndexedDB

这里先举个反例吧,如上图,随便打开 developer.mozilla.org/en-US/juejin.cn/ 两个网页,在各自的开发工具 Application 中就可以看出,如果非同源,是看不到各自的数据的。

相反,如下图,如果是同源的话,就可以互相看到数据

同源下,可以通过 AJAX 的方式互相发送、接收数据

这里还是举个反例吧,比如,在掘金上爬的这个获取今日推荐的接口(仅作为演示,无恶意调用,各位也不需要恶意调用喔)

fetch("https://api.juejin.cn/recommend_api/v1/article/recommend_all_feed?spider=0", {
  "method": "POST",
});

随便打开百度网页,打开谷歌开发者工具,执行上述 JS 脚本,就能看到熟悉的报错:

has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这个可能是我们经常会遇到的报错信息吧~

由此看出,如果非同源,不能通过 AJAX 的方式互相发送、接收数据;相反,如果同源,则可以通过 AJAX 的方式互相发送、接收数据。


至此就学完了本文的所有内容 (づ ̄3 ̄)づ╭❤~

老规矩,对文中任何一点有疑问的欢迎在评论区留言,一起完善文章内容 ( ̄▽ ̄)~*

如果各位看官对本专栏感兴趣的话,可以查看 不让面试题仅成为八股文 了解更多内容 \(^o^)/