You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过预签名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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 16:50:25