我的应用程序显示存储在 AWS S3 中的图像(出于安全原因,在私有存储桶中)。
要允许用户通过浏览器查看图片,我会生成已签名的网址,例如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端和/或客户端配置?
答案 0 :(得分:10)
此处的一种解决方法是阻止浏览器缓存下载的对象。这似乎源于S3可以说与Chrome处理缓存对象的方式相互作用的不正确行为。我最近回答了a similar question on Server Fault,您可以在那里找到其他详细信息。
当您从简单的HTML(如<img>
标记)中获取S3中的对象,然后在跨源上下文中再次获取相同的对象时,似乎会出现此问题。
Chrome会缓存第一个请求的结果,然后使用该缓存的响应,而不是第二次发出新请求。当它检查缓存的对象时,没有Access-Control-Allow-Origin
标头,因为它是从不受CORS规则约束的请求缓存的......所以当第一个请求发出时,浏览器没有发送{ {1}}标题。因此,S3没有使用Origin
标头(或任何与CORS相关的标头)进行响应。
问题的根似乎与HTTP Access-Control-Allow-Origin
响应标头有关,该标头与缓存有关。
Web服务器(在本例中为S3)可以使用Vary:
响应头向浏览器发出信号,表明服务器能够生成多个返回对象的表示形式 - 如果浏览器将变化请求的属性,响应可能不同。当浏览器考虑使用缓存对象时,它应该在得出缓存对象适合当前需要之前检查对象在新上下文中是否有效。
确实,当您向S3发送Vary:
请求标头时,您会收到包含Origin
的响应。这告诉浏览器,如果请求中发送的源是不同的值,则响应也可能不同 - 例如,因为并非所有源都可能被允许。
潜在问题的第一部分是S3 - 可以说 - 只要在存储桶上配置CORS,总是就会返回Vary: Origin
,即使浏览器没有发送原始标头,因为可以针对您实际未包含在请求中的标头指定Vary: Origin
,告诉您如果包含它,则响应可能有所不同。但是,当Vary
不存在时,它不会那样做。
问题的第二部分是Chrome - 当它查询其内部缓存时 - 发现它已经拥有该对象的副本。播放缓存的响应不包含Origin
,因此Chrome假定此对象对CORS请求也完全有效。显然,事实并非如此,因为当Chrome尝试使用该对象时,它会发现缺少跨源响应标头。据推测,如果Chrome在原始请求中收到了来自S3的Vary
响应,则会意识到其第二个请求的临时请求标头包含Vary: Origin
,因此它会正确地获取并获取不同的副本物体。如果它这样做,问题就会消失 - 正如我们通过在对象上设置Origin:
所示,阻止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: no-cache
元数据值(或缺少该元数据)。您不能简单地将此附加到查询字符串 - 您必须将其添加为URL签名过程的一部分。但是,一旦将其添加到代码中,您的对象将在响应标头中返回Cache-Control
。
或者,如果您在呈现页面时可以单独为同一对象生成这两个签名URL,只需更改其中一个签名URL相对于另一个签名URL的到期时间。让它延长一分钟,或沿着这些线。将过期时间从一个更改为另一个将强制两个签名的URL不同,并且Chrome将两个不同的对象与两个不同的查询字符串解释为两个单独的对象,这也应该消除第一个缓存对象的错误使用为其他请求服务。