被 CORS 数据和 S3 污染的画布

Posted

技术标签:

【中文标题】被 CORS 数据和 S3 污染的画布【英文标题】:Canvas tainted by CORS data and S3 【发布时间】:2017-12-05 12:22:05 【问题描述】:

我的应用程序正在显示存储在 AWS S3 中的图像(出于安全原因,在私有存储桶中)。

为了让用户从他们的浏览器中查看图像,我生成了 签名 URL,例如 https://s3.eu-central-1.amazonaws.com/my.bucket/stuff/images/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=20170701T195504Z&X-Amz-Expires=900&X-Amz-Signature=bbe277...3358e8&X-Amz-SignedHeaders=host。 这与<img src="S3URL" /> 完美配合:图像显示正确。 我什至可以通过复制/粘贴 URL 来直接在另一个选项卡中查看图像。

我还在生成嵌入这些图像的 PDF,这些图像需要在之前使用 canvas: 调整大小和水印进行转换。

但是我用来调整大小的库有一些问题:

Failed to execute 'getImageData' on 'CanvasRenderingContext2D':
The canvas has been tainted by cross-origin data.

确实,我们处于 CORS 上下文中,但我已经设置了所有内容,以便可以向用户显示图像并且确实可以正常工作。 所以我不确定这个错误的原因:这是另一个 CORS 安全层:浏览器担心我可能会出于恶意目的更改图像?

我尝试在 S3 存储桶上设置一个允许的 CORS 配置

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

img.crossOrigin = ""img.crossOrigin = "Anonymous" 在客户端,然后我得到:

Access to Image at 'https://s3.eu-central-1.amazonaws.com/...'
from origin 'http://localhost:5000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:5000' is therefore not allowed access.

可能缺少哪些 AWS/S3 端和/或客户端配置?

【问题讨论】:

两件事:临时修改S3中的对象,给对象添加Cache-Control: no-cache。 S3 和 Chrome 存在一个问题,如果您从 html &lt;img&gt; 访问相同的 S3 对象并在 CORS 上下文中单独访问,Chrome 似乎不会做正确的事情。然后清除浏览器缓存并查看它的行为,看看这是否确实相关。此外,使用 http://lvh.me:5000 而不是 localhost 访问您的测试站点,以查看 img.crossOrigin 的行为是否有任何不同。 感谢您的回答迈克尔。确实禁用缓存似乎可以解决问题。我通过切换每个图像Cache-Control 属性进行了一些测试,结果是一致的:Cache-Control 的图像可以使用画布进行转换。您能否在专门的答案中详细说明该问题,以便我接受。非常感谢您的出色帮助,我永远不会这样做。 :-) 【参考方案1】:

这里的一种解决方法是防止浏览器缓存下载的对象。这似乎源于 S3 与 Chrome 处理缓存对象的方式交互的部分不正确的行为。我最近回复了a similar question on Server Fault,您可以在那里找到更多详细信息。

当您从简单的 HTML(如 &lt;img&gt; 标记)从 S3 获取对象,然后在跨域上下文中再次获取相同的对象时,问题似乎出现了。

Chrome 会缓存第一个请求的结果,然后使用缓存的响应而不是第二次发出新请求。当它检查缓存的对象时,没有 Access-Control-Allow-Origin 标头,因为它是从不受 CORS 规则约束的请求中缓存的……所以当第一个请求发出时,浏览器没有发送 Origin标题。因此,S3 没有响应 Access-Control-Allow-Origin 标头(或任何与 CORS 相关的标头)。

问题的根源似乎与 HTTP Vary: 响应头有关,它与缓存有关。

Web 服务器(在本例中为 S3)可以使用 Vary: 响应标头向浏览器发出信号,表明服务器能够生成多个返回对象的表示形式——如果浏览器会 vary请求的一个属性,响应可能不同。当浏览器考虑使用缓存对象时,它应该检查对象在新上下文中是否有效,然后才能得出缓存对象适合当前需要的结论。

确实,当您向 S3 发送 Origin 请求标头时,您会收到包含 Vary: Origin 的响应。这会告诉浏览器,如果请求中发送的来源是不同的值,则响应也可能不同——例如,因为并非所有来源都被允许。

潜在问题的第一部分是 S3——可以说——应该总是在存储桶上配置 CORS 时返回Vary: Origin,即使浏览器没有发送原始标头,因为Vary 可以针对您实际上未包含在请求中的标头指定,以告诉您如果包含它,响应可能会有所不同。但是,当Origin 不存在时,它不会这样做。

问题的第二部分是 Chrome 在查询其内部缓存时发现它已经拥有该对象的副本。播种缓存的响应不包括Vary,因此 Chrome 假定此对象对于 CORS 请求也完全有效。显然不是,因为当 Chrome 尝试使用该对象时,它发现跨域响应标头丢失了。据推测,如果 Chrome 收到来自 S3 对原始请求的 Vary: Origin 响应,它会意识到第二个请求的临时请求标头包含 Origin:,因此它会正确地获取对象的不同副本。如果这样做,问题就会消失——正如我们通过在对象上设置 Cache-Control: no-cache 来说明的那样,防止 Chrome 缓存它。但是,它没有。

因此,我们通过在 S3 中的对象上设置 Cache-Control: no-cache 来解决此问题,这样 Chrome 就不会缓存第一个对象,而是为第二个对象发出正确的 CORS 请求,而不是尝试使用缓存的复制,这将失败。

请注意,如果您想避免更新 S3 中的对象以包含 Cache-Control: no-cache 响应,则还有另一个选项可以解决此问题,而无需实际将标头添加到 S3 中的静止对象。其实还有两个选择:

S3 API 尊重在查询字符串 response-cache-control=no-cache 中传递的值。将此添加到签名的 URL 将指示 S3 将标头添加到响应中,而不管与对象一起存储的 Cache-Control 元数据值(或缺少)。您不能简单地将其附加到查询字符串 - 您必须将其添加为 URL 签名过程的一部分。但是,一旦将其添加到代码中,您的对象将在响应标头中返回 Cache-Control: no-cache

或者,如果您可以在呈现页面时分别为同一个对象生成这两个签名 URL,只需更改其中一个签名 URL 相对于另一个的过期时间。把它延长一分钟,或者类似的东西。将过期时间从一个更改为另一个将强制两个签名 URL 不同,并且具有两个不同查询字符串的两个不同对象应该被 Chrome 解释为两个单独的对象,这也应该消除第一个缓存对象的错误使用服务另一个请求。

【讨论】:

感谢迈克尔的精彩回答。 :-) 这是一个非常微妙的问题!第一个解决方法对我来说似乎很完美,因为它将避免扫描已存储在存储桶中的所有图像以将其Cache-Control 属性切换为no-cache。我将实施并随时通知您。仅供参考,我在 IE11 (不幸的是)和 Firefox 上遇到了同样的问题,解决方法也为他们解决了问题。 好的,AWS 文档一如既往地稀缺,但由于 SO 上的另一个问题,我猜到了 getSignedUrl 的正确参数:ResponseCacheControl。生成签名 URL 的代码是 S3.getSignedUrl("getObject", Bucket: bucketName, Key: imageKey, Expires: duration, ResponseCacheControl: "no-cache" )。事实上,当使用签名的 URL 请求图像时,S3 正确地回答了 Cache-Control: no-cache 标头,并且原始错误已经消失。所以看起来你很摇滚,再次感谢。 :-) 而在客户端,您绝对需要在图像上设置crossOriginconst img = document.createElement("img"); img.crossOrigin = "anonymous"; img.addEventListener("load", e =&gt; ... ); img.src = imageS3SignedURL;

以上是关于被 CORS 数据和 S3 污染的画布的主要内容,如果未能解决你的问题,请参考以下文章

被 CORS 图像污染的画布

Amazon S3 图像,无法使用 html5 画布保存,出现受污染的画布错误

受污染的画布无法导出

有没有办法确定跨域图像是不是会在不绘制画布的情况下污染画布?

画布上的 CORS 在 Chrome 和 Safari 上不起作用

被跨域数据污染的画布