作为正确缓存行为所必需的,CloudFront 在将请求转发到源服务器之前会剥离请求中的几乎所有请求标头。
Referer
| CloudFront 删除标头。
http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#request-custom-headers-behavior
因此,如果您的存储桶尝试根据引用页面阻止请求(有时会这样做以防止热链接),则默认情况下,S3 将无法看到Referer
标头,因为 CloudFront 不会转发它。
而且,这是一个很好的例证whyCloudFront 不会转发它。如果 CloudFront 转发标头,然后盲目缓存结果,则存储桶策略是否具有预期效果将取决于第一个请求是来自预期站点之一还是来自其他地方 - 并且其他请求者将获得缓存的响应,这可能是wrong回复。
(tl;dr) 将其列入白名单Referer
用于转发到源的标头(在 CloudFront 缓存行为设置中)解决了此问题。
但是,有一个问题。
现在您正在转发Referer
标头到 S3,您已经扩展了缓存键-- CloudFront 缓存响应的事物列表 -- 包括Referer
header.
因此,现在,对于每个对象,CloudFront 将不会提供来自缓存的响应,除非传入请求的Referer
标头匹配exactly来自已缓存请求的一个...否则该请求必须发送到 S3。而且,关于引用标头的事情是引用page,不是指代site,所以每个page来自授权站点的这些资产将在 CloudFront 中拥有自己的缓存副本。
这本身并不是问题。这些额外的对象副本是免费的,这就是 CloudFront 的设计工作方式...问题是,它降低了给定对象位于给定边缘缓存中的可能性,因为每个对象必然会被引用较少。如果您的流量很大,那么这一点就变得不那么重要,甚至可以忽略不计;如果您的流量较小,那么这一点就变得更重要。更少的缓存命中意味着更慢的页面加载和更多的请求发送到 S3。
对于这是否适合您没有正确的答案,因为它非常具体于您使用 CloudFront 和 S3 的方式。
但是,这是另一种选择:
您可以删除Referer
通过将 CloudFront 配置为触发一个标头白名单中的标头转发到 S3 并消除对缓存命中产生负面影响的可能性Lambda@Edge 查看器请求触发器它将检查来自前门的每个请求,并阻止那些不是来自您希望允许的引用页面的请求。
查看器请求触发器在特定缓存行为匹配之后、检查实际缓存之前触发,并且大多数传入标头仍然完好无损。您可以允许请求继续进行(可以选择进行修改),也可以生成响应并取消其余的 CloudFront 处理。这就是我在下面要说明的——如果主机部分Referer
header 不在可接受值数组中,我们生成 403 响应;否则,请求将继续,检查缓存,并且仅根据需要查询源。
触发此触发器会给每个请求增加少量开销,但该开销可能会比降低缓存命中率更理想。因此,以下不是“更好”的解决方案——只是一个替代解决方案。
这是用 Node.js 6.10 编写的 Lambda 函数。
'use strict';
const allow_empty_referer = true;
const allowed_referers = ['example.com', 'example.net'];
exports.handler = (event, context, callback) => {
// extract the original request, and the headers from the request
const request = event.Records[0].cf.request;
const headers = request.headers;
// find the first referer header if present, and extract its value;
// then take http[s]://<--this-part-->/only/not/the/path.
// the || [])[0]) || {'value' : ''} construct is optimizing away some if(){ if(){ if(){ } } } validation
const referer_host = (((headers.referer || [])[0]) || {'value' : ''})['value'].split('/')[2];
// compare to the list, and immediately allow the request to proceed through CloudFront
// if we find a match
for(var i = allowed_referers.length; i--;)
{
if(referer_host == allowed_referers[i])
{
return callback(null,request);
}
}
// also test for no referer header value if we allowed that, above
// usually, you do want to allow this
if(allow_empty_referer && referer_host === "")
{
return callback(null,request);
}
// we did not find a reason to allow the request, so we deny it.
const response = {
status: '403',
statusDescription: 'Forbidden',
headers: {
'vary': [{ key: 'Vary', value: '*' }], // hint, but not too obvious
'cache-control': [{ key: 'Cache-Control', value: 'max-age=60' }], // browser-caching timer
'content-type': [{ key: 'Content-Type', value: 'text/plain' }], // can't return binary (yet?)
},
body: 'Access Denied\n',
};
callback(null, response);
};