跳到主要内容
  1. Skills/
  2. 前端编程/

令人爱恨交加的cors(跨域资源共享)

·字数 3204·7 分钟
cors

如果你是一位前端网页开发工程师,对cors(跨域资源共享) 的问题并不陌生。它是在前端开发中无法回避的问题。

开发出来的页面以为大功告成,可刷新页面时,总是样式加载不进来,打开 devtool 见到类似 firefox 中这样的错误提示:

已拦截跨源请求:同源策略禁止读取位于 https://.. 的远程资源。(原因:CORS 请求未能成功)。

此文档中不允许的 <script> 来源 URI:“https://...”。

已拦截跨源请求:同源策略禁止读取位于 https://.. 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。

mozilla 并于 CORS 的介绍 , 跨域资源共享

在 mozilla 的文章(上面的链接)中已经介绍得非常全面,不过对于新手来说,初次碰到这个问题,还是摸不着头脑,牵涉到几方,大概是遇到下面这样的情况:

┌──────────────────┐                   
│browser           │                   
│ ┌──────────────┐ │                   
│ │https://site1 │ │        ┌─────────┐
│ ├──────────────┤ │        │  site1  │
│ │  page1       ├─┼───────▶│         │
│ │              │ │        └─────────┘
│ │              │ │        ┌─────────┐
│ │              ├─┼──┐     │  site2  │
│ └──────────────┘ │  └────▶│         │
└──────────────────┘        └─────────┘

在页面 page1 上,引用了不是站点 site1 上的资源,如来自 site2cssjsjsonimages 等资源链接。关于 cors 的详细介绍请看 mozilla 的文章,有悟仅介绍简单的解决思路和方法,帮助你快速理解这个问题,当你解决多几次之后对问题有更深入了解,自然会有自己的更好的解决方案。

同一站点,是指 url 上的 host 一样,如果是二级域名,那二级域名也要相同。有悟在介绍为 网站申请 ssl证书时 ,谈及了域名站点的问题,其中就指出,相同主域不同子域的二级域名,也是不同站点的一种形式。

碰到上面的问题,不要去花时间检查代码逻辑是否正确,或者相是不是少写了很多代码的原因。这时所需要的 js、css、json 或者图片无法正常引入,生成的网页也是无法工作;也不用去搜索关于解决 CORS 的黑科技,这是浏览器强制性的要求,只能通过浏览器与服务端两个方面入手。

首先,CORS 问题不是作为客户端网面单方的问题,你需要对这些情况进行检查:

  1. 报错的那些 CORS 资源引用,css/js/json/image 是位于哪个站点?
  2. 如果这些资源跟你的站点不同,那么这些资源的服务端是否配置了’Access-Control-Allow-Origin’,这个非常关键

如果资源文件位于你页面的同一站点,你需要担心的并不是 CORS(因为在同一个域内,就根本不存在跨域问题),这时检查资源文件的路径是否正确即可;若果页面所引用的资源所在站点,在’Access-Control-Allow-Origin’上 做了屏蔽,那你应该果断放弃引用这个站点的资源,在页面上正规的方法是无法引用到的,这是浏览器为了安全与服务端之间所达成的约定。

Access-Control-Allow-Origin 它在服务端返回的http 响应头中的信息,可以通过 devtool 查看 。

经常出现的疑问 #

有些同学跟有悟初学 web 页面开发时一样,喜欢钻,一定会问,提示 CORS 错误被拦截的 URL请求,复制到浏览器地址直接访问、或者命令行用 curl 访问可以正常取到文件,那为什么在页面引入这个链接就不行呢?

这个要扯到 http 协议了。简单地讲,向服务端发起一个 http 访问请求时,除了向服务端发送 url 地址之外,还有一大堆附加信息,比如使用了什么客户端(user-agent)、请求什么类型的资源(accept)、来自哪里(refer)、cookie 等等信息(也就是请求头信息 http request header),都是客户端自动完成的。

在初次发起请求时,refer 可以为空,也可以人为指定,服务端会按照我们想让它知道的 refer 来工作,所以就可以像在地址栏、命令行 curl 上把资源文件取回来(服务端正常给出响应),但当请求是页面上的引用,那么浏览器会设定 refer 或者在 ajax (XMLHttpRequest 或者 fetch)的 origin ,而这个地方是不能修改的或者程序指定的,服务端才能知道请求的来源,根据这个来源判断是否正常响应或者给予拦截屏蔽。

正是因为这样,在我们通常能够发现,在网页上的 <script></script> 总是能保证 src 指向的资源能被获取到,后面会谈到一种叫 jsonp 的技术,它是解决 ajax 跨域访问的手段。

再次强调,不要去找能绕开这个机制的黑客技术,而是要了解这个机制、利用这个机制。

Access-Control-Allow-Origin=*,万事大吉 #

如果服务端的安全策略配置,Access-Control-Allow-Origin: *,那么无须折腾,直接引用便是。

不过,如果你对 子资源完整性(SRI) 并不是非常了解,想为 js/css 添加一段看似很酷的 integrity 检验码,与 crossorigin一同去掉。

<script type="text/javascript" src="..." integrity="sha256-Md8eaeo67OiouuXAi8t/Xpd8t2+IaJezATVTWbZqSOw="  crossorigin></script>

不然就会出现:

因为没有开启 CORS,也没有设置 same-origin,“https://youwu.today/scss/youwu.min.20cce7cd80bb4313b272c425d3fe27a92a1e3fbffe70ac6859707500aa47416d.css”无法用作完整性检查。

而声明 crossorigin 后,就出现下面的拦截

已拦截跨源请求:同源策略禁止读取位于 https://youwu.today/scss/youwu.min.20cce7cd80bb4313b272c425d3fe27a92a1e3fbffe70ac6859707500aa47416d.css 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。

要同源,或者开启 CORS,same-origin。问题又升级,对于新手来说,实在太复杂。

Access-Control-Allow-Origin 不允许你访问 #

如果服务端的 ’’ 不允许你访问,能做的就是把这里 js on、css、图片文件下载到你的站点中,然后把引用改为指向站点内的访问路径。

不少网站使用这个技术来实现图片防盗。

使用 iframe 来回避 CORS #

使用 iframe 来实现在一个网页中嵌套别一个站点的网页,如有悟文章 在 gohugo 中如何嵌入视频 中一样嵌套网页,可以回避CORS ,它在 iframe 内外形成了两个隔离环境。但这个方法,不应该是解决问题的主要办法,它能适用的场景有限,如果 iframe 中的子页面、与外部页面之间需要事件、数据交互,那就会变得非常复杂,或者难以实现你所想要的。

解决 fetch/ajax 的跨域问题 #

现在,在页面上使用 ajax、fetch 获取数据再动态刷新页面的技术实在太过普遍。fetch 是H5标准的浏览器 api,可以通过 js 在网面上获取数据,与 jquery ajax 类似,即使用浏览器原生 fetch函数,就不用为了使用 ajax 而引入 jquery。fetch 与 ajax 本身是不能绕开的 CORS。通过它们所获取的数据大都以 json 方式返回,json 资源同样受到上面所谈的 CORS约束。所以上面所列中解决方法原理都适用。

你在互联网上还可能搜索到一种叫 jsonp 的编程方式。其实它是上文 经常出现的疑问 中的到的,通过把数据获取的逻辑放到 <script></script>中,让浏览器来负责加载,就不会出现页面所在域与资源所在域不同的问题。

有技术爱好者模拟 fetch 用法实现了 jsonp 方式的开源库,如:

记住,这些方法并不是浏览器原生的CORS 解决机制(因为不存在),只是利用了浏览器本身的机制而已。

通过代理的方式 #

上面所谈的 <script></script>jsonpiframe 都是通过前端来解决。如果通过上面的方式都无法解决,或者非常麻烦,为了实现而要编写额外比较多的程序,那么可以采用部署后端代理的方式来代理这些站点外资源,让浏览器客户端认为这些资源与你的网页都是同一站点的。

总结 #

解决 CORS 问题,要结合服务端与网面二个方面来选定解决方案。

  • 若可以控制服务端,设定 ‘Access-Control-Allow-Origin’ ,将网页所在的站点添加到允许的范围内,这也是比较直接,但有时为了安全,你可能不想把它设置为 *;或者在规划允许之下,将资源与网页放在同一域名下。
  • 若无法控制服务端,那只能利用 http或者浏览器 的安全机制来寻求解决方案
    • iframe
    • <script></script> 或 jsonp
    • 若有条件部署服务器资源,可以部署代理

至此发现,cors 的问题并不会对前端网页代码量造成多大的增加。