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

S3预签名URL执行PUT请求返回403禁止错误求助

S3预签名URL PUT请求返回403 Forbidden的解决方法

问题场景

使用Next.js API生成S3预签名URL,前端通过PUT请求上传文件时返回403错误,已确认Content-Type无通配符且前后一致、请求头匹配,使用@aws-sdk/client-s3的S3Client无法设置signatureVersion: 'v4'。

后端API代码

import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { type NextApiRequest, type NextApiResponse } from "next";

const s3 = new S3Client({
  region: process.env.AWS_REGION + "",
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
  },
});

export default async function signUrl(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { fileName, fileType } = req.body;

  const s3Params = {
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: fileName,
    Expires: 3600,
    ContentType: fileType,
  };

  try {
    const command = new GetObjectCommand(s3Params);
    const url = await getSignedUrl(s3, command);
    res.status(200).json({ url });
  } catch (error) {
    console.log(error);
    res.status(500).end();
  }
}

前端调用代码

function handleLoadImage() {
  const input = document.createElement("input");
  input.type = "file";
  input.accept = "image/*";
  input.onchange = async () => {
    if (!input.files || !input.files[0]) return;
    const file = input.files[0];

    const response = await fetch("/api/signUrl", {
      method: "POST",
      body: JSON.stringify({ fileName: file.name, fileType: file.type }),
      headers: {
        "Content-Type": "application/json",
      },
    });
    if (!response.ok) throw new Error("Error generating signed URL");

    const { url } = await response.json();
    await fetch(url, {
      method: "PUT",
      body: file,
      headers: {
        "Content-Type": file.type,
      },
    });
    
  };
  input.click();
}

核心问题

后端API使用了GetObjectCommand生成预签名URL,该命令对应GET请求,但前端实际发送的是PUT请求,签名与请求方法不匹配,导致S3返回403 Forbidden。

解决步骤

1. 替换后端命令类型

将GetObjectCommand替换为PutObjectCommand,确保生成的预签名URL对应PUT请求:

修改后的后端API代码:

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; // 替换为PutObjectCommand
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { type NextApiRequest, type NextApiResponse } from "next";

const s3 = new S3Client({
  region: process.env.AWS_REGION + "",
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
  },
});

export default async function signUrl(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { fileName, fileType } = req.body;

  const s3Params = {
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: fileName,
    Expires: 3600,
    ContentType: fileType,
  };

  try {
    const command = new PutObjectCommand(s3Params); // 使用PutObjectCommand
    const url = await getSignedUrl(s3, command);
    res.status(200).json({ url });
  } catch (error) {
    console.log(error);
    res.status(500).end();
  }
}

2. 验证IAM权限

确保生成预签名URL使用的IAM用户具备s3:PutObject权限,示例IAM策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

同时检查Bucket策略是否允许该用户的Put操作,避免权限冲突。

3. 额外检查项

  • 确认fileName无特殊字符(如空格、中文等),若有需提前编码或处理,确保S3对象Key与预签名URL中的Key完全一致
  • 检查S3 Bucket是否开启了CORS,允许前端域名的PUT请求(若前端与API不在同一域名下):
    <?xml version="1.0" encoding="UTF-8"?>
    <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
      <CORSRule>
        <AllowedOrigin>https://your-frontend-domain.com</AllowedOrigin>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
      </CORSRule>
    </CORSConfiguration>
    

内容的提问来源于stack exchange,提问作者GermanJablo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:47:43