如何通过预签名URL限制S3对象仅可从自有网站下载?
解决方案:用预签名URL限制S3资源仅你的Next.js网站可访问
1. 立即关闭存储桶的公开只读权限
当前存储桶策略允许所有人读取对象,这是成本激增的核心原因,先替换原策略:
- 删除原
PublicRead语句,改为仅允许生成预签名URL的IAM实体(用户/角色)访问对象。
修改后的存储桶策略示例(替换YOUR_IAM_PRINCIPAL为你的IAM用户/角色ARN,例如arn:aws:iam::123456789012:user/nextjs-server):
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowServerAccess", "Effect": "Allow", "Principal": { "AWS": "YOUR_IAM_PRINCIPAL" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucketname/*" } ] }
如果Next.js部署在Lambda/ECS/EKS上,直接使用对应服务的执行角色ARN即可。
2. 配置IAM权限给Next.js服务端
确保运行Next.js的服务端实体拥有s3:GetObject权限,示例IAM政策:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucketname/*" } ] }
⚠️ 绝对不要在前端代码中暴露AWS密钥,预签名URL必须在服务端生成。
3. 在Next.js中生成预签名URL
分两种常用场景实现:
场景1:用API Route供客户端请求
创建app/api/get-presigned-url/route.js:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const s3Client = new S3Client({ region: "YOUR_REGION", // 生产环境优先用IAM角色自动获取权限,禁止硬编码密钥 }); export async function GET(request) { const { searchParams } = new URL(request.url); const key = searchParams.get("key"); // 目标S3对象的键,如"images/photo.jpg" if (!key) { return new Response(JSON.stringify({ error: "缺少对象Key" }), { status: 400 }); } try { const command = new GetObjectCommand({ Bucket: "bucketname", Key: key, }); // 设置URL过期时间,示例为5分钟(300秒) const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 }); return new Response(JSON.stringify({ url: signedUrl }), { status: 200 }); } catch (error) { return new Response(JSON.stringify({ error: error.message }), { status: 500 }); } }
前端调用API获取URL后加载资源:
// 客户端组件内 async function getResourceUrl(key) { const res = await fetch(`/api/get-presigned-url?key=${encodeURIComponent(key)}`); const data = await res.json(); return data.url; } // 使用示例 const imageUrl = await getResourceUrl("images/photo.jpg"); return <img src={imageUrl} alt="站点图片" />;
场景2:用Server Components直接生成
在Server Component中直接生成预签名URL,无需额外API:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const s3Client = new S3Client({ region: "YOUR_REGION" }); export default async function ResourceComponent({ key }) { const command = new GetObjectCommand({ Bucket: "bucketname", Key: key, }); const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 }); return <img src={signedUrl} alt="站点图片" />; }
4. 调整CORS配置限制跨域访问
当前CORS允许所有域名访问,修改为仅允许你的Next.js站点域名:
[ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "HEAD"], // 仅保留下载所需方法,删除PUT/POST/DELETE "AllowedOrigins": ["https://your-nextjs-domain.com", "http://localhost:3000"], // 替换为你的生产/开发域名 "ExposeHeaders": [ "x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2" ], "MaxAgeSeconds": 3000 } ]
额外注意事项
- 预签名URL过期时间可按需调整:图片展示用5-10分钟,大文件下载可设30分钟,避免URL被长期复用。
- 生产环境禁止硬编码AWS密钥,优先使用IAM角色(如EC2实例角色、Lambda执行角色)让服务自动获取权限。
- 修改权限后测试:直接访问对象的公开URL(如
https://bucketname.s3.region.amazonaws.com/images/photo.jpg)应返回403,确认公开访问已关闭。
内容的提问来源于stack exchange,提问作者NroGamerz
相关产品推荐
相关产品推荐

