S3 预签名 URL 是指向私有存储桶中某个对象的临时链接。它在查询字符串中携带签名,因此持有者无需 AWS 凭据即可在链接过期前下载(或上传)该对象,而存储桶永远不会变成公开的。你用自己的密钥在代码中或通过 AWS CLI 创建它,有效期可以从 1 秒到 7 天。本指南介绍它的工作原理、每个 X-Amz-* 参数的含义、它能持续多久、下载和上传的代码,以及你最可能遇到的错误。各项限制取自截至 2026 年 10 月的 AWS 和 Cloudflare 文档。
S3 预签名 URL 一览
| 问题 | 回答 |
|---|---|
| 它授予什么权限? | 对一个对象执行一项操作(GET、PUT、HEAD、DELETE 等) |
| 最长有效期 | 使用 SigV4 和 IAM 用户密钥时为 7 天(604,800 秒) |
| 使用临时密钥时的有效期 | 在角色会话或 STS 凭据结束时失效,以先到者为准 |
| S3 控制台范围 | 1 分钟到 12 小时 |
| 默认有效期 | AWS CLI 和 boto3 为 3,600 秒,JavaScript v3 presigner 为 900 秒 |
| 可以多次使用吗? | 可以,在过期前任何人想用多少次都行 |
| 可以撤销单个 URL 吗? | 不可以,只能撤销签名凭据或在策略中拒绝 |
| 创建时会调用 S3 吗? | 不会,签名在本地计算 |
预签名 URL 的工作原理
- 你的代码(或 CLI)接收存储桶、对象键、HTTP 方法和有效期。
- SDK 使用 AWS Signature Version 4,用你的私有访问密钥对这些值签名。此时不会向 S3 发送任何内容。
- 你把生成的 URL 交给某人:一个浏览器、一位同事、一个移动应用。
- 当对方使用它时,S3 用 URL 中访问密钥对应的私有密钥重新计算签名。如果签名匹配、URL 尚未过期,并且签名者有权执行该操作,请求就会通过。
URL 携带的是签名者的权限。AWS 对此说得很明确:如果生成 URL 的 IAM 用户或角色无法读取该对象,无论签名多么有效,URL 都会返回 403。
X-Amz 参数的含义
一个 SigV4 预签名 URL 看起来像这样(已添加换行):
https://my-bucket.s3.eu-west-1.amazonaws.com/reports/q3.pdf
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20261017%2Feu-west-1%2Fs3%2Faws4_request
&X-Amz-Date=20261017T093000Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=5f2c...e91a
| 参数 | 含义 |
|---|---|
X-Amz-Algorithm | 签名算法:SigV4 为 AWS4-HMAC-SHA256 |
X-Amz-Credential | 访问密钥 ID 加上范围:<key-id>/<YYYYMMDD>/<region>/s3/aws4_request |
X-Amz-Date | URL 的签名时间,UTC,格式为 yyyyMMddTHHmmssZ |
X-Amz-Expires | URL 在 X-Amz-Date 之后的有效秒数,从 1 到 604800 |
X-Amz-SignedHeaders | 签名覆盖的请求头,至少包含 host |
X-Amz-Signature | 签名本身,64 个十六进制字符 |
X-Amz-Security-Token | 仅在使用临时凭据(STS、角色、EC2、ECS、SSO)签名时出现 |
修改任何已签名的部分(对象键、方法、有效期或已签名的请求头),签名就不再匹配。还要注意 URL 以明文暴露的内容:存储桶名称、完整的对象键和你的访问密钥 ID。私有密钥永远不会出现在 URL 中。
预签名 URL 能持续多久
上限取决于签名所用的凭据:
| 签名凭据 | 最长有效期 |
|---|---|
| IAM 用户访问密钥(SigV4) | 7 天,因为 SigV4 签名密钥本身的有效期就是 7 天 |
| IAM 角色会话(STS AssumeRole) | 直到会话结束,默认 1 小时 |
| EC2 实例配置文件 | 直到角色凭据轮换,最长约 6 小时 |
| ECS 任务角色 | 直到任务凭据轮换,通常每 1 到 6 小时 |
| 任何已被撤销或删除的凭据 | 立即失效,无论 X-Amz-Expires 写的是多少 |
| S3 控制台 | 你可以在 1 分钟到 12 小时之间选择 |
这解释了最常见的意外:你在 Lambda 函数或 EC2 实例中请求了 7 天,但链接在一小时或几小时后就失效了。URL 的寿命不会超过其背后的凭据。如果你需要长期有效的链接,请使用一个只对该存储桶拥有读取权限的专用 IAM 用户来签名。
S3 在请求开始时检查有效期。在截止时间前一秒开始的大型下载会正常完成,但截止时间之后的重试会失败,返回 AccessDenied 和消息“Request has expired”。请求超过 604,800 秒会被直接拒绝。
如何生成预签名 URL
AWS CLI
aws s3 presign s3://my-bucket/reports/q3.pdf --expires-in 3600
该命令签发一个下载(GET)URL。默认为 3,600 秒,最长 604,800 秒。较新的 CLI 版本始终使用 SigV4,因此请确保已配置区域。上传 URL 需要使用 SDK。
Python(boto3)
import boto3
from botocore.config import Config
s3 = boto3.client('s3', region_name='eu-west-1', config=Config(signature_version='s3v4'))
url = s3.generate_presigned_url(
ClientMethod='get_object',
Params={
'Bucket': 'my-bucket',
'Key': 'reports/q3.pdf',
'ResponseContentDisposition': 'attachment; filename="q3.pdf"',
},
ExpiresIn=3600,
)
请显式设置 signature_version='s3v4'。boto3 参考文档提醒,出于向后兼容,否则它可能在仍接受旧版 Signature Version 2 的区域中使用该版本签名,从而悄悄丢弃一些参数和请求头,例如 Range。ResponseContentDisposition 是可选的:它让浏览器以干净的文件名保存文件,而不是在页面中直接打开。
JavaScript(AWS SDK v3)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({ region: 'eu-west-1' })
const url = await getSignedUrl(
client,
new GetObjectCommand({ Bucket: 'my-bucket', Key: 'reports/q3.pdf' }),
{ expiresIn: 3600 }
)
如果省略 expiresIn,JavaScript presigner 使用 900 秒(15 分钟)。
预签名上传 URL(PUT)
同样的思路也适用于反方向。你为某个对象键签发一个 PutObject,持有该 URL 的人就可以写入这个对象。文件从对方的设备直接传到存储桶,你的服务器从不处理这些数据。如果该键下已有对象,它会被替换。
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const client = new S3Client({ region: 'eu-west-1' })
const url = await getSignedUrl(
client,
new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'incoming/brief.pdf',
ContentType: 'application/pdf',
}),
{ expiresIn: 900, signableHeaders: new Set(['content-type']) }
)
await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/pdf' },
body: file,
})
将 content-type 加入 signableHeaders 会把 URL 绑定到该类型,因此使用不同 Content-Type 的上传会被拒绝。在 boto3 中,对应写法是 generate_presigned_url('put_object', Params={'Bucket': ..., 'Key': ..., 'ContentType': 'application/pdf'}),客户端必须发送相同的请求头。
从浏览器上传前要检查三件事:
- CORS。存储桶需要一条允许你的来源和
PUT方法的 CORS 规则。对于分段上传,它还必须暴露ETag请求头,因为浏览器需要从每个分段中读取它。 - 固定的键。在服务器上生成唯一的键(例如加上 UUID 前缀),这样访问者就无法覆盖别人的文件。
- 校验和。当前的 AWS SDK 默认会为上传添加 CRC 校验和。如果针对兼容 S3 的服务商的预签名上传因签名或校验和错误而失败,请在客户端上设置
requestChecksumCalculation: 'WHEN_REQUIRED'。
预签名 POST 是另一种用于普通 HTML 表单的机制,带有一个可以限制大小和内容类型的策略。并非所有兼容 S3 的服务商都支持它(R2 就不支持),所以预签名 PUT 是更具可移植性的选择。
大文件:预签名分段上传
单次 PUT 到 S3 的上限是 5 GB,AWS 建议从约 100 MB 起使用分段上传。分段上传将文件拆分为最多 10,000 个分段,除最后一个外,每个分段 5 MiB 到 5 GiB,各分段独立上传,可并行,可按分段重试。预签名按分段进行:服务器启动上传,为每个分段签发一个 UploadPart URL,浏览器发送每个数据块并回传其 ETag,最后由服务器完成上传。
import {
CreateMultipartUploadCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
} from '@aws-sdk/client-s3'
const { UploadId } = await client.send(
new CreateMultipartUploadCommand({ Bucket: 'my-bucket', Key: 'video/raw.mov' })
)
const partUrl = await getSignedUrl(
client,
new UploadPartCommand({ Bucket: 'my-bucket', Key: 'video/raw.mov', UploadId, PartNumber: 1 }),
{ expiresIn: 3600 }
)
await client.send(
new CompleteMultipartUploadCommand({
Bucket: 'my-bucket',
Key: 'video/raw.mov',
UploadId,
MultipartUpload: { Parts: [{ PartNumber: 1, ETag: etagFromPartResponse }] },
})
)
添加一条在几天后中止未完成分段上传的生命周期规则,否则被遗弃的分段会继续占用计费存储。
R2、B2、Spaces 和 Wasabi 上的预签名 URL
实现了 S3 API 并支持 SigV4 的服务商接受同样的代码:你只需更改端点、区域和密钥。细节有所不同,请查阅服务商的文档:
- Cloudflare R2 支持预签名的
GET、HEAD、PUT和DELETE,有效期从 1 秒到 7 天。它不支持预签名POST,而且预签名 URL 只能在 S3 API 域名(<ACCOUNT_ID>.r2.cloudflarestorage.com)上使用,不能用于自定义域名。请使用region: 'auto'。 - Backblaze B2 表示,其 S3 兼容 API 支持使用 AWS CLI 或 SDK 生成的预签名 URL。
- DigitalOcean Spaces 和 Wasabi 使用相同的 SDK 调用,只需指向它们的端点。
关于各服务商的价格,请参见 S3、R2、Spaces、Wasabi 与 B2 对比。
预签名 URL 的常见错误及解决方法
| 错误 | 常见原因 | 解决方法 |
|---|---|---|
SignatureDoesNotMatch | 方法、请求头或查询与签名时不同;时钟不同步 | 发送相同的 Content-Type 和请求头;用 NTP 同步时钟 |
AccessDenied “Request has expired” | URL 已超过 X-Amz-Date + X-Amz-Expires | 生成新的 URL;缩短签名与使用之间的间隔 |
| URL 比请求的时间更早过期 | 使用临时凭据签名(角色、EC2、ECS、Lambda) | 使用寿命不短于链接所需时长的凭据签名 |
ExpiredToken | X-Amz-Security-Token 背后的 STS 会话已结束 | 签名前先刷新凭据 |
新 URL 返回 403 AccessDenied | 签名者缺少 s3:GetObject 或 s3:PutObject,或有策略拒绝 | 为签名主体授予权限;检查存储桶策略 |
AuthorizationQueryParametersError | 有效期超过 604,800 秒,或凭据范围格式错误 | 让 X-Amz-Expires 不超过 7 天;在存储桶所在区域签名 |
| 浏览器被 CORS 阻止 | 存储桶没有针对你的来源和方法的 CORS 规则 | 添加包含你的来源、GET/PUT 并暴露 ETag 的 CORS 规则 |
AccessDenied 带 HeadersNotSigned | 对 Range 签了名,但发送了未签名的 If-Range | 将 If-Range 加入已签名的请求头 |
会改写请求头或查询字符串的企业代理也会破坏签名。如果一个 URL 在某个网络中可用而在另一个网络中不可用,请绕过代理测试。
安全最佳实践
AWS 将预签名 URL 称为持有者令牌,并建议加以保护。实际做法:
- 保持短时效。上传和敏感文件用几分钟,普通下载用几小时。有人再次需要文件时,重新生成一个。
- 用权限范围窄的身份签名。一个只能读取(或只能写入)相关前缀的专用 IAM 用户或角色,可以在链接泄露时限制损失。
- 在存储桶策略中设置防护。
s3:signatureAge条件会拒绝超过设定时长的签名,aws:SourceIp则限制哪些网络可以使用它们。 - 不要记录它们。应用日志、分析数据或聊天记录中的完整预签名 URL,在过期前都是有效的凭据。
- 为它们缺少的功能做好规划。它们没有密码、没有下载次数限制,也不能按链接撤销,所以当你需要这些功能时,请在前面放一个应用。
关于分享方面的深入介绍,以及能提供这些控制的方案,请参见 如何在不公开存储桶的情况下分享 S3 文件。
Nubbo 如何使用预签名 URL
Nubbo 正是基于这种模式构建的。当你在文件浏览器中上传时,服务器会签发一个 PUT(对于 100 MB 及以上的文件,则是三个分段并行的分段上传),你的浏览器把数据直接发送到你的存储桶。单文件下载使用有效期为一小时的预签名 GET URL。你的访问密钥使用 AES-256-GCM 加密保存,只在服务器上用于签名。
分享链接 补上了原始预签名 URL 所缺少的功能:可选的密码、到期日期和下载次数限制,Nubbo 会先检查这些条件,再发放短时有效的 URL,而且你可以随时停用链接。多文件 ZIP 下载在你的浏览器中打包,浏览器直接从你的存储桶下载每个文件:Nubbo 从不接触文件内容。只有一个需要如实说明的例外:缩略图会写入你自己存储桶的 .nubbo-thumbs/ 下。它在 Amazon S3、Cloudflare R2、DigitalOcean Spaces、Wasabi、Backblaze B2 和公网上兼容 S3 的端点上的工作方式都相同。
常见问题
S3 中的预签名 URL 是什么?
它是一个指向私有存储桶中某个对象的普通 HTTPS 链接,在查询字符串中附加了签名。持有该链接的人可以在其过期之前执行签名时指定的那一项操作,通常是下载(GET)或上传(PUT)。存储桶保持私有,使用链接的人也不需要 AWS 凭据。
S3 预签名 URL 的最长有效期是多少?
使用 Signature Version 4 和长期 IAM 用户密钥签名时为七天(604,800 秒)。使用临时凭据(例如所承担的角色或 EC2 实例配置文件)签名的 URL,会在这些凭据过期时失效,即使你请求了更长的时间。在 S3 控制台中,你可以选择 1 分钟到 12 小时。
X-Amz-Algorithm、X-Amz-Credential、X-Amz-Date、X-Amz-Expires、X-Amz-SignedHeaders 和 X-Amz-Signature 是什么意思?
它们是 SigV4 的身份验证参数。Algorithm 是签名方法(AWS4-HMAC-SHA256),Credential 是你的访问密钥 ID 加上日期、区域和服务范围,Date 是 URL 的签名时间,Expires 是它保持有效的秒数,SignedHeaders 列出签名所覆盖的请求头,Signature 就是签名本身。X-Amz-Security-Token 只在使用临时凭据签名时出现。
可以在 S3 预签名 URL 过期前撤销它吗?
不能单独撤销。只有当签名所用的凭据被撤销、删除或停用,或者存储桶策略拒绝该请求时,预签名 URL 才会提前失效。如果你需要取消单个链接,请在前面放一个应用,让它在完成自己的检查后才发放短时有效的 URL。
S3 预签名 URL 安全吗?
签名无法伪造或篡改,存储桶也保持私有。但 AWS 将预签名 URL 描述为持有者令牌:任何拿到链接的人都可以使用它,次数不限,直到过期。请保持较短的有效期,使用 HTTPS,并避免把链接发布在可能被复制的地方。
预签名 URL 能用于 Cloudflare R2、Backblaze B2 或 Wasabi 吗?
实现了 S3 API 并支持 Signature Version 4 的服务商通常都支持,你只需使用相同的 SDK 调用并指向服务商的端点。例如 Cloudflare R2 支持预签名的 GET、HEAD、PUT 和 DELETE,有效期从 1 秒到 7 天,但不支持 POST 表单上传,也不支持自定义域名。
为什么我的预签名 URL 返回 SignatureDoesNotMatch?
AWS 列出了三个常见原因:计算机时钟不同步、代理修改了请求头或查询字符串,以及请求的方法、请求头或参数与签名时的不同。上传时,最常见的原因是发送的 Content-Type 与生成 URL 时使用的不同。
来源
价格和限制来自以下官方页面。服务商可能会调整,请在做决定前核实。