画布被 CORS 数据和 S3 污染



我的应用程序正在显示存储在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方面的错误行为,该行为与Chrome处理缓存对象的方式进行了交互。 我最近回答了一个关于服务器故障的类似问题,您可以在那里找到更多详细信息。

当您从简单的 HTML(如<img>标签)从 S3 获取对象,然后在跨源上下文中再次获取同一对象时,似乎会出现问题。

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

问题的根源似乎与 HTTPVary:响应标头有关,该标头与缓存有关。

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

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

潜在问题的第一部分是,S3 - 可以说 - 应该始终返回Vary: Origin,只要在存储桶上配置了 CORS,即使浏览器没有发送源标头,因为可以针对您实际上未包含在请求中的标头指定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 应将具有两个不同查询字符串的两个不同对象解释为两个单独的对象,这也应消除错误地使用第一个缓存对象来为另一个请求提供服务。

我已经允许 S3 上的任何源,即使我只从一个源获取,但这个问题仍然存在,所以我看不出这实际上如何成为 CORS 问题。正如其他人所说,这主要是浏览器错误。如果您必须从标签中获取图像并在以后的任何时候也为其执行 javascript 获取,则会出现问题......你会得到缓存错误。对我来说,最简单的解决方案是将查询参数放在 javascript 将使用的 URL 的末尾。

https://something.s3.amazonaws.com/something.jpg?stopGivingMeHeadaches=true

最新更新