文章

S3 预签名 URL:是什么、如何工作、有效期限制与代码示例

什么是 S3 预签名 URL,每个 X-Amz 参数的含义,7 天有效期上限,上传和下载代码(CLI、boto3、JS),以及常见错误的解决方法。

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 的工作原理

  1. 你的代码(或 CLI)接收存储桶、对象键、HTTP 方法和有效期。
  2. SDK 使用 AWS Signature Version 4,用你的私有访问密钥对这些值签名。此时不会向 S3 发送任何内容。
  3. 你把生成的 URL 交给某人:一个浏览器、一位同事、一个移动应用。
  4. 当对方使用它时,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-DateURL 的签名时间,UTC,格式为 yyyyMMddTHHmmssZ
X-Amz-ExpiresURL 在 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)使用寿命不短于链接所需时长的凭据签名
ExpiredTokenX-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 时使用的不同。

来源

价格和限制来自以下官方页面。服务商可能会调整,请在做决定前核实。