S3直传时签名URL过期了怎么办?

FSD-绍桐 阅读 65

我用预签名URL上传文件到S3,但用户上传大文件时经常提示“SignatureDoesNotMatch”或403错误,估计是URL过期了。试过把过期时间设成1小时,但还是不够用。

有没有办法在前端检测URL是否快过期,或者动态续期?现在用的逻辑是后端生成一次URL就直接给前端用了:

const uploadFile = async (file) => {
  const presignedUrl = await fetchPresignedUrl(); // 后端返回的URL
  await fetch(presignedUrl, { method: 'PUT', body: file });
};
我来解答 赞 14 收藏
二维码
手机扫码查看
2 条解答
UX纳利
UX纳利 Lv1
大文件上传用预签名URL本身就有点坑,签名是一次性的,过期就是过期了,没法在前端"续期"。

最靠谱的方案是上分片上传(Multipart Upload)。S3支持把一个大文件拆成多个小片(比如5MB一片)并行上传,每片都有独立的签名,某个分片失败了只需要重传那一片就行,不用整个文件重来。而且S3会在后台合并分片,体验上和普通上传没区别。

具体实现思路:

后端生成预签名URL时,不要只生成一个整文件的URL,而是生成一组分片的签名URL,返回给前端大概这样的结构:
{
uploadId: "abc123",
partUrls: [
{ partNumber: 1, url: "https://s3...part=1&signature=..." },
{ partNumber: 2, url: "https://s3...part=2&signature=..." },
// ...
]
}


前端拿到后遍历这些URL并行上传,每个上传完拿到ETag存起来,全部传完后再调一次后端接口完成合并。

如果不想自己写分片逻辑,市面上有现成的库可以用,比如aws-sdk-js的MultipartUploader,或者直接用插件。WordPress这边的话,WP Offload Media插件本身就支持分片上传,配置一下S3 bucket和CloudFront就能用,省心。

至于你说的"检测即将过期",这个思路不太对。签名URL本质上是"一次性"的,无法延期。正确的做法是:要么用足够短的超时时间(比如15分钟)+ 错误重试机制;要么直接上分片上传,一劳永逸。
点赞
2026-03-19 23:07
百里翌菡
大文件上传用预签名URL本身就是坑,S3的签名URL设计出来就不是让你传几个G的东西跑几小时的。

最佳方案:分片上传(Multipart Upload)

这是AWS官方推荐的大文件上传方式,把文件切成多个part并行上传,每个part独立签名、独立校验,某个part失败重传那个就行,不用整个文件重来。而且没有URL过期的问题——只要整个upload流程在7天内完成就行。

简单说下流程:
1. 后端调用S3的createMultipartUpload拿到uploadId
2. 前端把文件切片(通常5MB-100MB一个part),对每个part请求后端生成预签名URL
3. 前端并行上传各个part,全部完成后调用后端完成分片上传

后端用AWS SDK大概是这样:

const { S3Client, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } = require('@aws-sdk/client-s3');

async function createMultipartUpload(key, contentType) {
const command = new CreateMultipartUploadCommand({
Bucket: process.env.BUCKET,
Key: key,
ContentType: contentType
});
return s3Client.send(command); // 返回 UploadId
}

async function getPartUploadUrl(key, uploadId, partNumber) {
const command = new UploadPartCommand({
Bucket: process.env.BUCKET,
Key: key,
UploadId: uploadId,
PartNumber: partNumber
});
// 生成该part的预签名URL
return s3Client.send(command);
}

async function completeUpload(key, uploadId, parts) {
const command = new CompleteMultipartUploadCommand({
Bucket: process.env.BUCKET,
Key: key,
UploadId: uploadId,
MultipartUpload: { Parts: parts }
});
return s3Client.send(command);
}


前端大概逻辑:

const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB

async function uploadLargeFile(file) {
const uploadId = await createUpload(file.name, file.type);
const totalParts = Math.ceil(file.size / CHUNK_SIZE);
const parts = [];

for (let i = 0; i < totalParts; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);

// 并行上传part
const partUrl = await getPartUploadUrl(file.name, uploadId, i + 1);
await fetch(partUrl, { method: 'PUT', body: chunk });

parts.push({ ETag: 'xxx', PartNumber: i + 1 });
}

await completeUpload(file.name, uploadId, parts);
}


如果实在不想改分片,还有个凑合的方案:前端做个定时器,快过期(比如剩余1分钟)时提前重新请求新URL。但说实话,大文件用这个方案就是埋雷,指不定哪个part就超时了。

AWS SDK for JavaScript v3本身就有封装好的upload方法,内部自动做分片,你也可以直接用那个,更省事:

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";

const upload = new Upload({
client: new S3Client({ region: "us-east-1" }),
params: {
Bucket: "your-bucket",
Key: "file-key",
Body: fileObject
}
});

await upload.done();


这个Upload类内部自动处理分片、失败重试、并行上传,比自己写省心多了。唯一需要后端做的,就是确保前端有合法的AWS凭证(用Cognito或者临时STS凭证)。

自己选吧,要省心就用SDK的Upload,要完全控制就自己写分片。
点赞
2026-03-18 09:08