令人爱恨交加的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 上的资源,如来自 site2 的 css、js、json、images 等资源链接。关于 cors 的详细介绍请看 mozilla 的文章,有悟仅介绍简单的解决思路和方法,帮助你快速理解这个问题,当你解决多几次之后对问题有更深入了解,自然会有自己的更好的解决方案。
同一站点,是指 url 上的 host 一样,如果是二级域名,那二级域名也要相同。有悟在介绍为 网站申请 ssl证书时 ,谈及了域名站点的问题,其中就指出,相同主域不同子域的二级域名,也是不同站点的一种形式。
碰到上面的问题,不要去花时间检查代码逻辑是否正确,或者相是不是少写了很多代码的原因。这时所需要的 js、css、json 或者图片无法正常引入,生成的网页也是无法工作;也不用去搜索关于解决 CORS 的黑科技,这是浏览器强制性的要求,只能通过浏览器与服务端两个方面入手。
首先,CORS 问题不是作为客户端网面单方的问题,你需要对这些情况进行检查:
- 报错的那些 CORS 资源引用,css/js/json/image 是位于哪个站点?
- 如果这些资源跟你的站点不同,那么这些资源的服务端是否配置了’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>
、jsonp、iframe 都是通过前端来解决。如果通过上面的方式都无法解决,或者非常麻烦,为了实现而要编写额外比较多的程序,那么可以采用部署后端代理的方式来代理这些站点外资源,让浏览器客户端认为这些资源与你的网页都是同一站点的。
总结 #
解决 CORS 问题,要结合服务端与网面二个方面来选定解决方案。
- 若可以控制服务端,设定 ‘Access-Control-Allow-Origin’ ,将网页所在的站点添加到允许的范围内,这也是比较直接,但有时为了安全,你可能不想把它设置为
*
;或者在规划允许之下,将资源与网页放在同一域名下。 - 若无法控制服务端,那只能利用 http或者浏览器 的安全机制来寻求解决方案
- iframe
<script></script>
或 jsonp- 若有条件部署服务器资源,可以部署代理
至此发现,cors 的问题并不会对前端网页代码量造成多大的增加。