CloudFront 作为 AWS 的 CDN 服务,默认只根据请求的 URL 和查询字符串来决定是否命中缓存。这意味着如果两个请求指向同一个路径,但携带了不同的自定义请求头,CloudFront 会认为它们是相同的请求,直接返回缓存中的第一个响应。对于需要根据 User-Agent 返回不同页面、根据 Accept-Language 返回不同语言内容、或者根据 Authorization 头区分用户权限的场景,这种默认行为完全无法满足需求。Header Forwarding 就是为解决这个问题而设计的,它允许你把指定的请求头转发到源站,同时把这些头纳入 CloudFront 的缓存键,从而实现基于头部的差异化缓存。

理解 Header Forwarding 的关键在于区分两个独立但相关的动作:转发和缓存。转发指的是 CloudFront 把客户端发送的某些 HTTP 头原样传递给源站,源站可以依据这些头生成个性化响应;缓存则是 CloudFront 在决定是否复用已有响应时,是否把这些头的值也作为区分条件。如果只转发但不纳入缓存键,那么第一个请求的响应会被缓存并返回给所有后续请求,即使它们携带了不同的头值,这显然会造成内容错乱。因此,Header Forwarding 的配置必须同时兼顾转发列表和缓存键策略。
Header Forwarding 的三种模式与缓存键机制
在 CloudFront 分配(Distribution)的行为(Behavior)配置中,Header Forwarding 有三个选项:None、Whitelist 和 All。None 意味着不转发任何自定义请求头,只转发少数 CloudFront 自动处理的头(如 Host、User-Agent 的部分信息);Whitelist 允许你显式指定一个头部名称列表,只有列表中的头会被转发到源站并参与缓存键;All 则转发所有客户端发送的头,但这种方式在生产环境中非常不推荐,因为任何一个头值的变化都会导致缓存未命中,产生大量缓存碎片,同时也会把不必要的敏感信息暴露给源站。
缓存键的完整构成包括:请求的 URL 路径、查询字符串、Cookie(如果配置了 Forward Cookies)、以及 Header Forwarding 中白名单列出的请求头。CloudFront 会计算这些值的组合哈希,作为缓存对象的唯一标识。因此,即使两个请求的 URL 完全相同,只要白名单中的某个请求头值不同,CloudFront 就会将它们视为不同的缓存条目,分别向源站发起请求并各自缓存。例如,白名单包含 Accept-Language,那么 /index.html 的英文响应和中文响应会作为两个独立对象存储,后续携带相同语言头的请求可以直接命中对应缓存。
需要特别注意 CloudFront 对某些头有默认处理规则。比如 Host 头始终会被转发给源站,但它不参与缓存键,因为 Host 通常由 CloudFront 根据分配域名自动设置;User-Agent 默认会被截断并传递给源站,但不会作为缓存键的一部分。如果你希望基于完整的 User-Agent 或者其子串来区分移动端和桌面端,就必须把 User-Agent 加入白名单。另外,CloudFront 对头部名称的大小写不敏感,但值的大小写敏感,这意味着 Accept-Language: en-US 和 Accept-Language: en-us 会被当作两个不同的缓存键。
通过控制台和 CLI 配置 Header Forwarding
在 AWS 管理控制台中,进入 CloudFront 分配,选择对应的行为,点击编辑。在“Cache key and origin requests”部分,可以看到“Headers”下拉菜单。选择“Include the following headers”,然后从预定义列表中选择或手动输入头部名称。预定义列表涵盖了常见的 CORS 头、客户端 IP 相关头、设备检测头等,你还可以添加任意自定义头。保存后,CloudFront 会自动更新边缘节点的配置,大约需要几分钟到十几分钟生效。
如果使用 AWS CLI 或 SDK 进行自动化部署,需要更新 DistributionConfig 中对应 CacheBehavior 的 ForwardedValues 或 CachePolicy。较新的推荐做法是使用 Cache Policy 和 Origin Request Policy 分离缓存键与源站请求头。以下示例展示了创建一个缓存策略,将 Accept-Language 和 User-Agent 纳入缓存键,同时创建一个源站请求策略,将 Authorization 头转发给源站但不纳入缓存键:
aws cloudfront create-cache-policy \
--cache-policy-config '{
"Name": "my-language-cache-policy",
"MinTTL": 0,
"MaxTTL": 31536000,
"DefaultTTL": 86400,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 2,
"Items": ["Accept-Language", "User-Agent"]
}
},
"CookiesConfig": {"CookieBehavior": "none"},
"QueryStringsConfig": {"QueryStringBehavior": "none"}
}
}' \
--region us-east-1
aws cloudfront create-origin-request-policy \
--origin-request-policy-config '{
"Name": "my-auth-origin-request-policy",
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 1,
"Items": ["Authorization"]
}
},
"CookiesConfig": {"CookieBehavior": "none"},
"QueryStringsConfig": {"QueryStringBehavior": "none"}
}' \
--region us-east-1
上面的命令创建了两个独立策略。缓存策略决定哪些头参与缓存键,源站请求策略决定哪些头被转发给源站。两者可以独立配置,实现更精细的控制。之后在更新分配行为时,将这两个策略的 ID 关联到对应的 CacheBehavior 即可。使用分离策略的好处是,你可以对同一个分配下的不同路径应用不同的缓存逻辑,而不需要修改每个行为的整体配置。
如果继续使用旧版 ForwardedValues 配置,也不复杂。在 JSON 中设置 Headers 字段为白名单列表即可:
{
"ForwardedValues": {
"QueryString": false,
"Cookies": {"Forward": "none"},
"Headers": {
"Quantity": 2,
"Items": ["Accept-Language", "User-Agent"]
}
}
}
需要注意的是,旧版配置中的 Headers 白名单不仅用于源站转发,也会自动参与缓存键,无法像新策略那样分离。因此对于需要转发认证头但不想让认证头破坏缓存的情况,强烈建议迁移到 Cache Policy 和 Origin Request Policy。
Header Forwarding 对缓存命中率与源站负载的影响
将请求头纳入缓存键会直接降低缓存命中率,增加回源请求数量。假设一个 URL 原本只有一个缓存对象,加入 Accept-Language 白名单后,如果用户群体有 10 种常用语言,理论上最多会产生 10 个缓存版本。如果再加入 User-Agent,移动端和桌面端又各自成倍增加。如果头部值非常分散,例如 Authorization 头中每个用户都有不同的 Bearer Token,那么缓存将完全失效,每个请求都会回源,CDN 的加速作用荡然无存。
因此,选择白名单头时必须评估该头的基数(Cardinality)。基数低、值集合有限的头适合纳入缓存键,比如 Accept-Language、Device-Type、国家代码等。基数高、每个用户都不同的头绝对不应该纳入缓存键,比如 Authorization、Cookie 中的会话 ID、X-Request-ID 等。对于这类头,正确的做法是只转发到源站而不纳入缓存键,也就是使用 Origin Request Policy 单独转发。这样源站可以执行鉴权逻辑,但 CloudFront 仍能基于 URL 或其他低基数字段高效缓存公共内容。
另一个常被忽略的影响是缓存碎片化带来的成本上升。CloudFront 的缓存存储在边缘节点,每个边缘节点独立维护缓存。当缓存键维度过多时,单个边缘节点的有效缓存容量会被大量低访问频率的副本占用,导致高访问频率的内容被提前逐出。这不仅降低命中率,还可能导致源站收到突发的集中回源请求。生产环境中建议定期分析 CloudFront 的缓存命中率指标,如果发现命中率因 Header Forwarding 配置而显著下降,应重新审视白名单列表,考虑是否可以用查询字符串或路径前缀来替代头值区分。
典型场景实践与常见配置误区
场景一:多语言站点。源站根据 Accept-Language 返回不同语言版本。建议只将 Accept-Language 加入缓存键白名单,并且最好在边缘使用 Lambda@Edge 或 CloudFront Functions 对语言头进行归一化处理,例如把 en-US、en-GB 都归一化为 en,把 zh-CN、zh-TW 归一化为 zh,这样可以减少缓存版本数量。归一化后的值再作为自定义头传给源站,同时用于缓存键。
场景二:移动端与桌面端差异化页面。源站根据 User-Agent 中的 Mobile 关键字返回不同布局。如果直接把完整 User-Agent 加入白名单,会导致缓存版本爆炸,因为 User-Agent 字符串种类极多。正确做法是用 CloudFront Functions 在边缘解析 User-Agent,提取一个二值头(如 X-Device-Type: mobile 或 desktop),只把这个自定义头加入缓存键。这样缓存版本数为 2,同时源站也能收到明确的设备类型标识。
场景三:需要鉴权的 API 响应。比如同一个 GET /api/products 对登录用户返回个性化价格,对匿名用户返回标准价格。此时不应将 Authorization 头加入缓存键,否则所有用户都会各自回源。更合理的方案是源站返回响应时设置 Vary: Authorization 响应头,同时 CloudFront 配置 Origin Request Policy 转发 Authorization。但 CloudFront 默认不根据响应 Vary 头来分割缓存,因此更好的做法是在边缘用 CloudFront Functions 读取 Authorization,提取用户组标识(如 X-User-Tier),只把这个低基数标识加入缓存键,并转发给源站。
常见误区包括:把 CloudFront 的“All headers”选项当作万能解药;认为只要转发了头就会自动按头缓存;在缓存键中加入高基数字段导致命中率崩溃;忘记在源站设置 CORS 相关头时考虑 Preflight 请求的缓存。另外,如果源站返回的响应包含 Vary 头,CloudFront 并不会像传统 HTTP 缓存那样根据 Vary 自动拆分缓存,你必须显式配置对应的 Header Forwarding 或 Cache Policy 才能实现类似效果。理解这些边界,才能让 Header Forwarding 真正服务于业务而不是拖累性能。

