S3跨域资源共享(CORS),始终发送Vary:Origin

26

我正在使用启用了CORS的CloudFront后面的S3存储桶。如果客户端使用Origin头发出请求,则S3(和CloudFront)会响应一个“Vary: Origin”头,但是如果请求没有带上Origin头,则响应中不包含任何Vary头。

这是有问题的,因为我在img标签中使用来自cloudfront/s3的资源,在这种情况下,浏览器会在没有Origin头的情况下发出请求,然后稍后再为该图像发出ajax请求。浏览器随后使用缓存版本的图像,其中不包含Access-Control-Allow-Origin头文件,因此拒绝该请求。

是否有办法让S3始终返回“Vary: Origin”头?


5
这个问题也在AWS论坛上被报告了。 - Kristian Hanekamp
3个回答

37

我注册了一个帐户来回答你的问题,因为针对这种问题(以及一些相关问题)很少有好的答案。

你描述的问题主要在Chrome浏览器中出现,但FireFox和IE似乎聪明到不会在这些情况下在AJAX和常规调用之间共享缓存。

问题

让我们先描述一下问题为未来的读者:

  • 浏览器(Chrome)使用常规的<img><script>标签向服务器请求。如果服务器位于同一域中,则不包含CORS标头。
  • 服务器(S3)返回资源。如果请求中没有Origin标头,则不会在响应中附加CORS标头,因为它们是多余的。
  • 浏览器(Chrome)再次尝试使用AJAX获取资源,但这次实际上并没有连接到服务器而是查看缓存的资源。
  • 浏览器(Chrome)的缓存版本没有CORS标头。它将放弃请求,作为Access-Control-Allow-Origin违规或其他相关问题。

解决方案

在HTML5中,有一个称为crossorigin的属性,可以添加到标记中表示它们需要发送原始信息。可能的值为crossorigin='anonymous'crossorigin='use-credentials',它们与所提出的问题没有太大关系,但正如文档中所说:

默认情况下(即未指定该属性时),根本不使用CORS。

https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes

因此,只需像这样创建您的图像标记:<img src='cloundfront.path' crossorigin='use-credentials'>

就是这样。这很模糊,所以我希望这个答案能为一大堆人节省些研究时间。


2
我猜测crossorigin="use-credentials"相当于在XHR请求中调用withCredentials,对吗?如果是这样的话,crossorigin="anonymous"是否同样适用,以确保CORS标头得到返回?如果可能的话,我不想让我的静态图像设置cookie等功能。 - Jeremy T
1
这个缓存问题在我们的Chrome扩展中让我苦恼了很久。特别是在具有CORS的Chrome扩展中,需要考虑的东西太多了。这个答案很清晰,帮助我们找到了问题的症结。谢谢。另外,只是为了参考,如果你想避免图像属性“crossorigin”,你可以使用cache:no-store进行fetch请求,这将绕过缓存。 - theHarsh
我同意@JeremyT的观点,我认为crossorigin="anonymous"是处理静态资源的更佳方法。当启用cors时,这是解决我在获取缓存图像时遇到的问题的方法。 - JoeMoe1984

15
另一个解决方案是配置您的CloudFront分发以自动将非CORS请求转换为CORS请求。这可以通过在每个请求中添加CORS头部来实现,CloudFront使用最近添加的"控制边缘到原点请求标头"功能向S3发送请求。
请参阅此处的功能公告: https://aws.amazon.com/blogs/aws/cloudfront-update-https-tls-v1-1v1-2-to-the-origin-addmodify-headers/ 并查看此处的文档: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/forward-custom-headers.html

2
很好,那会强制 Access-Control-Allow-Origin,但对于 Vary: Origin 呢? - caub
1
如果您在每个请求上强制使用Access-Control-Allow-Origin,则不再需要“Vary:Origin”,因为标头在请求之间不再变化。如果浏览器使用缓存版本,那么这个版本也支持CORS,这也不再成为问题。 - Kristian Hanekamp
7
然而,如果你强制CloudFront始终向S3发送"Origin: X.domain.com",那么CloudFront就无法返回"Access-Control-Allow-Origin: Y.domain.com"。换句话说,这种强制头部解决方案只在你返回一个"Access-Control-Allow-Origin: *"通配符响应头或者在"Access-Control-Allow-Origin"头部中只返回单个域名时有效。 - timmfin
我们强制CloudFront将Origin发送到S3:phhh。在s3上配置了CORS。S3认为Origin与主机不同,然后用ACA *标头进行响应。因此,每个请求都会得到CORS标头,而不是变化。 - Ivan Borshchov

1
我偶然发现了一个简单的方法,可以让Cloudfront始终添加“Vary: Origin”头信息,尽管据我所知并未记录在案:您可以通过在CloudFront缓存键中包含“Origin”来强制使用“Vary”标头。
在Cloudfront分发中,单击“编辑行为”,找到“缓存密钥和源请求”标题。如果您使用“传统缓存设置”,则在“添加标头”下选择“Origin”。如果您使用较新的缓存策略,则需要单击“创建策略”,并在“缓存密钥设置”下添加Origin,然后返回并使用刚刚创建的策略。
我的用例是HTML5视频字幕轨道,如果您使用“crossorigin =“ anonymous””,则不会发送Origin,如果没有Origin,则没有Vary:Origin。

嗨,看起来ETag没有改变。因此,即使使用头Vary:origin chrome仍然使用缓存。你有解决办法吗? - Hugo Mallet
对于静态的S3文件,etag通常不会改变。如果etag没有改变,那么使用缓存版本就可以了,这就是etag的作用。 - Jan M
问题在于,如果文件第一次被缓存时没有包含CORS头,则使用crossorigin =“anonymous”的第二个下载将使用相同的缓存并失败。 - Hugo Mallet
是的,但请参考Kristian Hanekamp上面提供的解决方案,这个问题现在已经不再存在于新的功能更新中。 - Jan M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接