CloudFront+S3架构下基于User-Agent的访问控制实现及缓存、Lambda@Edge权限问题解决方案
嘿,我来帮你一步步拆解问题并解决。你现在遇到两个核心问题:一是CloudFront的缓存绕过了S3桶策略的访问控制,二是Lambda@Edge在Viewer Request事件上触发时出现503权限错误。咱们逐个解决:
一、为什么S3桶策略的控制会被缓存绕过?
CloudFront的核心作用是缓存源站内容到边缘节点。当第一个带有合法User-Agent: example的请求到达时,CloudFront会从S3拉取内容并缓存。后续不管请求的User-Agent是否合法,只要缓存键匹配,就直接返回缓存内容——完全不会再去请求S3,自然也就绕过了S3桶策略的检查。
所以要解决这个问题,要么让CloudFront的缓存区分合法/非法请求的User-Agent,要么在缓存层(边缘节点)直接拦截非法请求。
二、方案1:调整CloudFront缓存策略(配合S3桶策略)
如果你想继续用S3桶策略控制访问,必须让CloudFront根据User-Agent生成不同的缓存条目:
- 进入CloudFront控制台,找到你的分发,切换到「行为」标签,编辑目标行为
- 在「缓存策略」部分,选择「创建新缓存策略」
- 在缓存键设置中,添加
User-Agent作为自定义头部(只添加这一个即可,避免缓存条目过多膨胀) - 保存策略并关联到你的分发行为
这样CloudFront会为不同的User-Agent缓存不同内容:合法请求的内容会被缓存,非法请求的403响应也会被缓存,后续同User-Agent的请求直接命中对应缓存,不会绕过控制。
但这个方案的缺点是:非法请求仍会回源一次(直到缓存了403响应),不如在边缘直接拦截高效。
三、方案2:用Lambda@Edge在Viewer Request阶段拦截(推荐)
Viewer Request事件是在CloudFront边缘节点接收请求后、检查缓存前触发的,能直接拦截非法请求,完全不会回源,性能更好。之前的503错误是因为Lambda的权限配置不对,按以下步骤修复:
1. 配置Lambda执行角色的信任策略
Lambda@Edge的Viewer Request函数需要允许CloudFront服务角色调用它:
- 打开IAM控制台,找到Lambda的执行角色
- 修改信任策略,添加CloudFront的服务主体:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" }, { "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::你的账号ID:distribution/你的分发ID" } } } ] }
2. 正确发布Lambda到边缘
- Lambda@Edge的Viewer Request函数必须在**us-east-1(弗吉尼亚北部)**区域创建
- 编写完函数后,必须发布一个正式版本(不能用
$LATEST版本,CloudFront不支持) - 关联到CloudFront的Viewer Request事件时,选择刚才发布的版本,而非
$LATEST
3. Lambda函数示例代码
这个函数会检查请求的User-Agent是否为example,不符合则直接返回403:
exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const userAgentHeader = request.headers['user-agent']; // 检查User-Agent是否存在且严格等于"example" if (!userAgentHeader || userAgentHeader[0].value !== 'example') { const forbiddenResponse = { status: '403', statusDescription: 'Forbidden', body: 'Access Denied: Invalid User-Agent', headers: { 'content-type': [{ key: 'Content-Type', value: 'text/plain' }] } }; callback(null, forbiddenResponse); return; } // 符合条件,继续处理请求(检查缓存或回源) callback(null, request); };
4. 简化S3桶策略
当用Viewer Request拦截后,非法请求根本不会到达S3,所以可以移除桶策略里的AWS:UserAgent条件,只保留允许CloudFront访问的规则:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowCloudFrontServicePrincipalReadOnly", "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::<bucket-name>/*", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::account-number:distribution/EXAMPLEDISTO" } } } ] }
四、为什么Origin Request能正常运行?
Origin Request事件是在CloudFront准备回源时触发的,这个阶段的Lambda权限要求更低,因为它是在你的AWS区域(而非边缘节点)执行的,所以不会出现503错误。但缺点是:非法请求仍会到达S3,浪费源站资源,而且还是需要调整缓存策略避免缓存绕过,不如Viewer Request高效。
总结
优先选择方案2(Lambda@Edge Viewer Request),既解决了缓存绕过问题,又能在边缘拦截非法请求,性能最优。只要配置好Lambda的信任策略和发布版本,就能解决之前的503错误。
内容的提问来源于stack exchange,提问作者Yahav

