日期:2021年5月15日标签:JavaScript

谷歌浏览器缓存导致跨域,造成tainted canvas错误 #

最近工作中遇到了一个canvas绘制image的错误。是由于canvas在使用其他源的图片的时候,造成跨域导致,遇到这种问题一般我们只需要将图片的crossorigin设置下,然后请求响应头也需要设置access-control-allow-origin,即可解决问题。但是我遇到的问题比这要复杂一些,因为在前端和服务端都设置好的情况下,我的问题依然存在,依然报跨域。接下来我将详细解释相关内容。

cors图片

一. 同源限制 #

因为问题产生的根本原因时浏览器的**同源限制(same-origin-policy)**导致,所以我将简要介绍下这个概念。

浏览器安全的根基就是同源限制,何为同源限制呢,当且仅当两个域名满足以下三点,我们认为这两个域名是同源的:

  • 协议相同
  • 域名相同
  • 端口相同

有一点需要注意的是,标准规定的端口相同的才同源,但是浏览器并没有遵循这一项规定,也就是说如果两个网站协议相同,域名相同但是端口不相同,那么浏览器也认为其是同源的,例如http:://www.pengfeixc.com:8000http://www.pengfeixc.com:8001这两个地址只有端口不一样,它们是不同源的,但是由于浏览器并未遵循这一规定,所以浏览器认为它们是同源的,所以它们是可以互相读取cookie的。

同源限制的目的是为了保证互联网的用户安全信息的,如果没有同源限制,不同源的网站可以互相读取cookie的话,那么会很危险的。设想一下,如果网站A把用户信息存储在cookie中,如果没有同源限制的情况下,网站B也可以读取网站A存储的cookie来获取用户信息,如果是比较重要的用户信息,那么后果将不敢想象。

所以出现了CORS(cross origin resource share),即跨域资源共享。在请求非同源资源的时候必须通过CORS方式请求。 关于CORS,我就不在展开了,它就是一种请求非同源资源的方式。可以在https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 上获取详细信息。

二.crossorigin属性 #

html5中一些html元素增加了crossorigin属性,提供了对cors的支持。例如<audio><img><link><script><video>都可以通过设置crossorigin属性来改变普通请求方式为cors请求。以img为例

<!-- 以非跨域请求方式请求/1.jpg -->
<img src="/1.jpg"  alt="图片" />
<!-- 以cors请求方式请求http://www.example.com/image/1.jpg -->
<img src="http://www.example.com/image/1.jpg" alt="图片" crossorigin="anonymous" />

crossorigin有如下取值:

  • anonymous:执行一个cors请求,但是该请求不会发送相关证书,例如cookie。服务器需要相应的设置Access-control-Allow-Origin响应头,图片才是未被污染的。

  • use-credentials: 执行一个cors请求,该请求会发送相关证书,携带cookie和http基本验证信息。服务器需要相应设置Access-Control-Allow-Origin响应头。

如果没有显示设置crossorigin属性,那么请求会是非跨域请求,如果图片来自非同源地址,那么该图片认为是被污染的。如果我们在canvas上绘制该图片,那么canvas将被认为是污染(tainted)的,被污染的canvas不能使用如下方法:

  • 在canvas context上调用getImageData()
  • 在canvas元素上调用toBlob()
  • 在canvas元素上调用toDataURL()

总结就是我们不能在被污染的canvas上获取图片相关信息,所以要避免这中问题,就需要设置图片的crossorigin属性,当然服务端也需要设置响应头。

三.浏览器缓存 #

我在查阅了很多资料后,image的crossorigin属性也设置了,一般应该是没有问题了,但是浏览器还是报错,图片跨域!!!

整个人快整懵了,崩溃了。后来发现罪魁祸首是浏览器缓存导致的。因为我在css样式中提前引入了非同源地址的图片作为背景图,代码类似如下:

{
    background: url("http://www.example.com/1.jpg");
}

背景图的url与当前网站是非同源的,css在请求的时候是以普通请求(非cors方式)请求的,所以请求到的图片是污染的图片,在canvas中直接使用后,肯定是不能获取该图片数据的(getImageData、toBlob和toDataURL方法报错)。虽然我在canvas中使用的图片已经设置过了crossorigin属性但是其地址也是http://www.example.com/1.jpg,与我上面用到的背景图地址相同,谷歌浏览器为了用户体验,直接使用缓存的图片,也就是背景图,但是这背景图是以非同源方式请求的啊。这下该怎么办。我又不能设置css背景图以cors方式请求,html并没有提供类似的corssorigin属性来设置background image。所以要么我更改背景图片,要么另寻他法。

四.解决 #

发现了问题,就要去解决。在查找了大量的资料,最后找到了一个方法,就是在每次请求图片的时候加一个timestamp参数,这样可以强制浏览器不使用缓存的图片。每次在canvas上绘制图片时,重新请求的图片地址加上当前的时间戳参数。

`http:://www.example.com/1.jpg?timestamp=${Date.now()}`

因为每次请求的地址不一样,所以浏览器不可能在从缓存中找到它认为相同的图片了。至此问题终于被解决了😄。

(完)

目录