入门指南
预签名 URL 的七天不是通用上限:四层时限,与签名不匹配的六条排查
预签名 URL 提前失效或报 SignatureDoesNotMatch 时,先分清是 URL 到期、凭据到期、凭据被吊销,还是签名年龄策略;再按六条固定顺序核对,而不是先怀疑时钟。
URL 没过期,不代表凭据还在。

先看答案
- 适合谁
- 把预签名 URL 直接发给客户或前端、过期后只会重新生成的人
- 遇到 SignatureDoesNotMatch 就先去校时的人
- 用临时凭据签发链接、结果链接比预期早失效的人
- 你会获得
- 先算清四层时限,再谈签名错。
- 发布时间
- 实质更新
- 发布
- AOPC 编辑部
60 秒结论
一分钟看懂这篇文章
URL 没过期,不代表凭据还在。
人负责
- 判断失效发生在哪一层
- 决定链接给谁、给多久
AI 负责
- 按六条顺序复现 SignatureDoesNotMatch
- 核对 Content-Type 与签名头
- 读错误
- 算时限
- 验凭据
- 复现
直接答案
「预签名 URL 不是能设 7 天吗?怎么第二天就失效了。」
因为 7 天只是其中一层的上限,而且是最宽松的一层。一条预签名 URL 真正能活多久,由四个彼此独立的时钟共同决定,谁先到点谁说了算:
| 层 | 由什么决定 | 文档给出的取值 | 到点后的表现 |
|---|---|---|---|
| ① URL 自带的过期时间 | 生成时指定的时间间隔 | 控制台 1 分钟–12 小时;CLI/SDK 最高 7 天 | 请求被拒,通常要重签 |
| ② 凭据本身的寿命 | 签发者用的是长期凭据还是临时凭据 | IAM 角色会话到期即失效;EC2 实例角色约 6 小时;ECS 任务角色 1–6 小时轮换;STS AssumeRole 默认会话 1 小时 |
链接比设定时间更早失效 |
| ③ 凭据的状态 | 凭据是否被吊销、删除、停用 | 没有“更晚的过期时间”能救 | 立即失效 |
| ④ 签名年龄策略 | 桶策略里的 s3:signatureAge 等条件 |
由你自己写;文档示例是「超过 10 分钟」 | 签名本身没错,但请求被拒 |
文档把第 ②③ 层写得很直白:预签名 URL 在「配置的过期时间」或「其关联凭据过期」两者中较早的那个时刻失效(原文:whichever occurs first);而且「一般来说,当你用来创建它的凭据被吊销、删除或停用时,预签名 URL 就会过期。即使这条 URL 是用更晚的过期时间创建的,也是如此。」
所以排查顺序不该是「先校时」,而该是「先算清这条 URL 受哪几层约束」。
另外,先别把所有失败都叫「过期」。文档里至少出现四种不同的文本,指向四处不同的地方:
| 错误文本 | 文档给的含义 | 先查什么 |
|---|---|---|
SignatureDoesNotMatch |
签名算出来对不上 | 第四节六条顺序 |
ExpiredToken |
用来生成 URL 的凭据已失效 | 换凭据重签,不是给 URL 续期 |
AccessDenied 且带 HeadersNotSigned: if-range |
签名头里有 Range,但请求带了未签名的 If-Range |
重签,并把 If-Range 纳入签名头 |
普通 403 Forbidden |
生成者缺该操作的权限,或桶策略显式拒绝 | 权限与桶策略,不是时钟 |
状态码相同不代表原因相同。 先读响应体里的错误文本,再决定查哪一层(把错误文本作为分诊入口属编辑判断,不是文档给的分类表)。
一、四层时限,逐层都能让链接先死
第一层:URL 自己写着的过期时间
生成预签名 URL 时,你必须提供凭据、桶、对象键、HTTP 方法和一个过期时间间隔。这条 URL 可以被多次使用,直到那个时刻为止(原文:You can use the presigned URL multiple times, up to the expiration date and time)——它不是一次性链接,这也意味着任何拿到它的人,在你设定的时间窗内都能反复用。
控制台能设的范围是 1 分钟到 12 小时;CLI 或 SDK 能设到 7 天。这两个数字不是同一件事,下一节展开。
第二层:凭据本身的寿命
如果签发用的是临时凭据,URL 不可能活得比凭据久(原文:Can't be valid for longer than the credentials themselves):
- IAM 角色:角色会话结束时 URL 就失效,「即使你指定了更长的过期时间」。
- EC2 实例角色:有效期等于角色凭据的时长,文档写「typically 6 hours」。
- ECS 任务/容器:FAQ 里写角色凭据通常每 1–6 小时轮换一次。
- STS
AssumeRole:角色会话结束即失效,默认会话时长 1 小时。 - 其它 STS 临时凭据:只在临时凭据的有效期内有效。
这条最容易被忽略,因为签发代码里明明写了 ExpiresIn: 604800,看起来是 7 天。实际能活多久,取决于那段代码拿的是哪一类凭据。
第三层:凭据的状态
凭据被吊销、删除或停用,URL 立刻失效——这一层与「还有多少天到期」无关。文档在 FAQ 里把原因说成「凭据不再有效」(ExpiredToken),处置是刷新凭据后重新签发,而不是想办法延长旧 URL。
第四层:签名年龄策略
这一层不是 URL 的过期时间,而是桶策略对「签名有多老」的限制。文档给的示例策略用 s3:signatureAge 拒绝签名超过 10 分钟的请求,条件值是 600000(与 10 分钟对应,即毫秒):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Deny a presigned URL request if the signature is more than 10 min old",
"Effect": "Deny",
"Principal": { "AWS": "*" },
"Action": "s3:*",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"NumericGreaterThan": { "s3:signatureAge": "600000" }
}
}
]
}
10 分钟是示例,不是默认值。 但如果你的桶上真有一条这样的策略,那么「URL 设了 7 天」和「签名只能活 10 分钟」会同时成立,而后者赢——签名没错、URL 没过期,请求照样被拒。
还有一层不属于「时间」,但表现一样:网络路径
桶策略或 IAM 策略可以用 aws:SourceIp(公网端点)或 aws:SourceVpc/aws:SourceVpce(VPC 端点)限制请求来源。文档明确说这类限制「也适用于预签名 URL 场景之外」,反过来就是:一条没过期的 URL,换个网络就可能用不了。如果你在办公室能开、客户那边打不开,先别怀疑过期时间。
二、7 天到底是谁的上限
把文档里散落的数字并排放在一起,结论很清楚:
| 签发方式 | 最长有效期 | 出处要点 |
|---|---|---|
| S3 控制台「Share with a presigned URL」 | 12 小时 | 控制台可分享最大 5 TB 的对象;最长期限为创建后 12 小时 |
AWS CLI aws s3 presign --expires-in |
7 天 | 示例 --expires-in 604800(秒) |
AWS SDK ExpiresIn |
最高 7 天 | 上传示例里用的是 1000 秒,只是示例值 |
| IAM 用户凭据 + SigV4 | 最高 7 天 | 原文限定为「when you’re using AWS Signature Version 4」 |
| 临时凭据(角色/STS/EC2/ECS) | 不超过凭据寿命 | 角色会话、轮换周期可能远短于 7 天 |
所以「预签名 URL 最长 7 天」这句话,只对「用 IAM 用户长期凭据 + SigV4 + CLI/SDK 签发」这一种组合成立。控制台签发的是 12 小时;临时凭据签发的是「凭据还剩多久」。
还有一个签发细节:2019 年 3 月 20 日之后上线的所有区域,用 CLI 签发时要显式给出 --region 与 --endpoint-url。文档给的是示例区域,本文不复述区域清单——按你桶所在区域填。如果签发端与使用端对区域的认知不一致,报错也可能落在签名类错误里,而不是一句「区域错了」。
三、过期是在「收到请求时」检查的
这条规则决定了很多「看起来矛盾」的现象。原文:
Amazon S3 checks the expiration date and time of a signed URL at the time of the HTTP request.
推论(原文自己给了):如果客户端在过期前一刻开始下载一个大文件,下载会继续,即使下载过程中过期时间到了;但如果连接断了,客户端在过期之后尝试续传,这次请求就失败。
对一人公司的实际含义:
- 正在传的不要慌:大文件传到一半跨过了过期点,当前这条连接通常不受影响。
- 断点续传要重签:别把「刚才还能下」当成「现在还能续」。
- 不要用「重试同一个 URL」当兜底:重试发生在过期之后,重试多少次都是同一个结果;正确动作是重新签发,再从中断处继续。
- 批处理要按最慢的那一个算:给一批对象签发同一批链接时,时限要覆盖整批任务,而不是第一条开始的时间(这条属编辑判断)。
四、SignatureDoesNotMatch 的六条固定顺序
上传页把排查写成六条,顺序本身就是建议的检查顺序:
- 系统时间是否与可靠时间服务器同步。 文档的原话是「即使很小的时间漂移也可能使签名失效」。
- URL 是否原样使用、没有任何修改。 包括被聊天工具、工单系统、邮件客户端改写,或复制时被折行、被转义。
- URL 是否已经过期。 过期就重新生成,不要在旧 URL 上做实验。
- 上传请求的
Content-Type是否与生成 URL 时指定的相同。 这条只影响上传类 URL,但极其常见。 - 区域是否是桶所在的那个区域。
- 用
curl时是否给 URL 加了引号。 URL 里的特殊字符不加引号会被 shell 吃掉。
FAQ 里还补了两条同样重要的:
- 公司代理可能改写请求头或查询串,从而导致签名不匹配;排查办法是「绕过代理再试一次」。
- 所有请求参数——HTTP 方法、请求头、查询串——必须在生成与使用之间完全一致。
把这两组合成一张分诊表:
| 你看到的现象 | 更可能的原因 | 下一步 |
|---|---|---|
| 本机能用、同事/客户不能用 | 代理改写、网络路径限制、区域/端点不一致 | 换网络直连复测;核对 aws:SourceIp 类策略 |
| 下载 URL 好用,上传 URL 报签名错 | Content-Type 与生成时不一致 |
客户端显式发送同一个值 |
| 刚生成就报签名错 | 系统时钟漂移、URL 被改写、区域错 | 校时;逐字符比对;核对桶区域 |
| 用了几天后报签名错 | 已过期,或凭据被轮换/吊销 | 重签;查凭据来源与寿命 |
AccessDenied + HeadersNotSigned: if-range |
带 Range 的请求又带了未签名的 If-Range |
生成时把 If-Range 加入签名头 |
ExpiredToken |
生成 URL 的凭据本身失效 | 刷新凭据,重新生成 |
表格的分组是编辑判断;每一行的原因都能在来源页找到对应原文。
关于 If-Range 这个坑
文档单列了一条 FAQ:当 Range 出现在 X-Amz-SignedHeaders 里时,如果请求带了 If-Range,S3 要求 If-Range 也必须被签名,否则返回 AccessDenied 并带 HeadersNotSigned: if-range。
这意味着:同一段“断点续传”代码,加了 If-Range 之后可能需要重新设计签名头。它不是过期问题,也不是时钟问题,但会被很多人归到「签名莫名其妙坏了」里去。
五、上传用的预签名 URL,有两处与下载不同
同一个 key 会覆盖,不是新增
上传页写得很清楚:如果桶里已经存在 URL 指定的同名 key,S3 会用上传的对象替换已有对象;上传完成后,桶的所有者拥有该对象。
所以一条上传 URL 不是「给你一个位置放文件」,而是「给你一次写这个 key 的权限」。把上传 URL 发给外部时,要想清楚三件事(编辑判断):
- 这个 key 是不是每次唯一(加随机前缀/时间戳),否则后一次上传会覆盖前一次;
- 覆盖是不是你想要的(比如「替换头像」就是想要);
- 上传者能不能借这条 URL 写到别的 key(不能——key 在签发时就固定了,但这也意味着一个 key 一条 URL)。
Content-Type 要一致,校验和要用对版本
上传示例里,生成 URL 时传了 ContentType,随后用 curl 上传时也必须带同样的 Content-Type;文档还专门提醒「如果你生成 URL 时指定了不同的 content type,请确保在 curl 命令里使用那个相同的 content type」。
校验和方面:SigV2 签发的 URL 只支持 MD5;SigV4 支持更多算法——CRC-64/NVME、CRC32、CRC32C、SHA-1、SHA-256、MD5、XXHash64、XXHash3、XXHash128、SHA-512,条件是使用 SigV4 并在上传请求里带上对应的校验和头。换句话说,「我传完了,内容对不对」这件事,取决于你有没有在请求里真的带上校验和,而不是取决于 URL 本身。
六、权限与签名是两条独立的线
预签名 URL 不创建新权限,它只是把签发者已有的权限临时借出去:
- 任何人都能用有效凭据生成 URL,但要让别人访问成功,生成者本人必须拥有该操作的权限(例如
s3:GetObject)。 - URL 的能力受生成者权限限制;文档把预签名 URL 称作 bearer token——谁持有,谁就能用,所以「保护好它」是签发者的责任。
- FAQ 对 403 的第一条解释就是权限:生成 URL 的 IAM 用户或角色要有对应权限,且桶策略不能显式拒绝。
这解释了一类很常见的误判:换一个更有权限的人重新签发就好了,改 URL 的过期时间没用。签名负责证明「这条 URL 是谁签的」,权限负责决定「这个人能不能做这件事」,两者互不替代。
七、十分钟核对顺序
下面这套顺序的原则是:从最便宜、最确定的证据开始,逐层排除。命令是示例,语法以你本机为准。
第 1 分钟:读错误文本。
不要只看状态码。看响应体里的错误码与消息,先归到上一节那张分诊表里的某一行。
第 2 分钟:算 URL 自己的过期时刻。
文档示例的 URL 形态是 ?AWSAccessKeyId=...&Signature=...&Expires=1741978496,其中 Expires 是 Unix 秒。macOS 上可以这样换算:
date -u -r 1741978496
(GNU date 用 date -u -d @1741978496;这是环境差异,不是 S3 的规则。若你的 URL 是另一种签名版本的参数形态,按生成器输出的字段名找对应值。)
第 3 分钟:看本机时间。
date -u
与可信时间源比对。文档要求与可靠时间服务器同步;漂移多大会失败,文档没有给阈值,本文也不编一个。
第 4 分钟:确认凭据来源与剩余寿命。
这一项没法从 URL 反推,只能回到签发代码/配置:是 IAM 用户长期凭据,还是角色、STS、EC2、ECS 的临时凭据?如果是后者,会话或轮换周期还剩多久?
第 5 分钟:原样重放一次。
curl -sS -D - -o /dev/null "<presigned-url>"
三条纪律:URL 加引号;一个字符都不改;不要在浏览器里先「打开看看」再复制地址栏(地址栏可能已经改写)。
第 6 分钟:换一条网络路径。
如果直连成功、走代理失败,问题在代理改写请求头或查询串;如果换网络就失败,回头查 aws:SourceIp/aws:SourceVpc 一类限制。
第 7 分钟:核对区域与端点。
尤其是 2019-03-20 之后上线的区域,CLI 签发要显式给 --region 与 --endpoint-url。
第 8 分钟:如果是上传 URL,核对 Content-Type 与校验和头。
生成时用什么,请求时就发什么;用了校验和,就确认头真的带上了。
第 9 分钟:确认桶策略里有没有「签名年龄」类条件。
搜 s3:signatureAge。有的话,URL 的过期时间再长也没用。
第 10 分钟:把结论写成一句话。
「这条链接失效,是因为第 ② 层凭据到期」——写不出这句话,说明还没定位,别急着重新生成一批链接发给客户。
十二项上线前核对
- 签发用的是哪类凭据?长期、角色、STS、EC2、ECS,寿命各不相同。
- 代码里写的过期时间,是否小于凭据剩余寿命?大于的部分不会生效。
- 控制台签发的链接是不是被当成了 7 天?控制台上限是 12 小时。
- 链接是不是发给了不该拿到的人?它是 bearer token,谁拿到谁能用。
- 链接会被重复使用吗?在过期前可以多次使用,不是一次性。
- 上传 URL 的 key 是否唯一?同名会覆盖已有对象。
- 上传时的
Content-Type是否与签发时一致? - 用了校验和吗?SigV2 只支持 MD5,SigV4 才支持更多算法。
- 2019-03-20 后上线的区域,签发时给
--region与--endpoint-url了吗? - 桶策略里有
s3:signatureAge或网络路径限制吗? - 断点续传会重签吗?重试旧 URL 在过期后必然失败。
- 失败时看的是错误文本还是状态码?四种文本指向四个地方。
八、六种常见误读
误读一:「预签名 URL 最长就是 7 天。」 控制台签发上限 12 小时;CLI/SDK 才到 7 天;临时凭据还要再短。把 7 天当成所有场景的上限,会导致「为什么客户第二天就打不开」这种问题永远解释不清。
误读二:「没过期就一定能用。」 凭据被吊销、删除、停用会立即失效,即使 URL 写了更晚的时间;角色会话到期、网络路径限制、桶策略拒绝,也都能让一条「时间上没过期」的 URL 失败。
误读三:「SignatureDoesNotMatch 就是时钟问题。」
时钟只是六条里的第一条。URL 被改写、已过期、Content-Type 不一致、区域不对、curl 没加引号,以及代理改写请求,都会给出同一个错误文本。
误读四:「预签名 URL 可以单独撤销。」 文档只说凭据被吊销、删除、停用时 URL 失效,没有给「撤销某一条 URL」的机制。可控的手段是短时限 + 重新签发,以及收紧签发者的权限——不是去后台找「删除这条链接」的按钮。
误读五:「URL 一过期,正在下载的文件立刻断。」 过期是在收到 HTTP 请求时检查的。下载中途跨过过期点,当前连接通常继续;断线后重连才会失败。这个区别决定了你要不要为「大文件传到一半」做特殊处理。
误读六:「上传 URL 只是给对方一个位置。」 同一个 key 会被覆盖,上传后对象归桶所有者。它是一次写权限,不是一块空白画布。
九、本周就能做的五件事
- 把签发逻辑里的「过期时间」改成按凭据类型计算。 临时凭据场景下,取「请求的时限」与「凭据剩余寿命」的较小值,并在代码注释里写明原因。这一条能消掉大部分「链接提前失效」的工单。
- 在签发处记录三样东西:凭据来源、凭据到期时间、URL 到期时间。 不要记录完整 URL(它是 bearer token,日志泄露等于把权限发出去);记录 key 与两个时间点就够了。
- 给上传 URL 固定
Content-Type,并让客户端显式发送同一个值。 上传失败里「签名错」有相当一部分是这一条。 - 把第七节的十分钟顺序存成一张检查卡。 下次有人报「链接打不开」,按顺序走,而不是先改过期时间、再校时、再重装 SDK。
- 对客户链接默认用短时限。 既然没有单条撤销机制,缩短窗口就是最实际的风险控制;需要更长时间时,让对方在到期前换一条新链接。
如果只能记住一件事:预签名 URL 的失效时间,是四个时钟里最早到的那个,不是你在代码里写的那个。
边界说明:本文关于预签名 URL 的陈述全部来自 Amazon S3 用户指南的三个公开页面,均于 2026-10-30 逐条访问并验证为 HTTP 200(字节数见「来源」)。不写「7 天是预签名 URL 的通用上限」——控制台是 1 分钟–12 小时,CLI/SDK 才是最高 7 天,临时凭据还受凭据寿命约束;不写「没过期就一定能用」——凭据吊销/删除/停用、角色会话到期、网络路径限制、桶策略都能让有效期内的时间设置失效;不写「签名错一定是时钟」——文档给了六条排查,时钟只是第一条;不把 s3:signatureAge 示例里的 10 分钟(600000)说成默认值;不写「预签名 URL 可以单条撤销」——文档没有给出该机制;不写具体区域清单与端点域名(原文只给示例);不写任何签名失败率、过期比例或性能数字。文中标注为编辑判断的有:第一节按错误文本分诊的四行表、第三节「批处理按最慢的一条算」、第五节的三条覆盖风险提示、第四节分诊表的归组、第七节的核对顺序与命令示例(含 macOS 与 GNU date 的差异)、第九节的五件事。本文只覆盖 S3 通用存储桶的预签名 URL 语义,不涉及其它兼容 S3 协议的服务实现差异,也不重复浏览器预检与 CORS 头的排查(见《取图能显示、上传却 403》一篇)。
来源
以下页面均于 2026-10-30 逐条访问并验证为 HTTP 200;字节数为当日抓取的实际大小。
- Download and upload objects with presigned URLs核查于 2026/10/30(200,28 379 B):预签名 URL 的用途与「凭据属于生成它的 IAM principal」;同 key 上传会「replaces the existing object」;「You can use the presigned URL multiple times, up to the expiration date and time.」;签发需要桶、key、HTTP 方法与过期间隔;SigV2 只支持 MD5、SigV4 支持 CRC-64/NVME、CRC32、CRC32C、SHA-1、SHA-256、MD5、XXHash64、XXHash3、XXHash128、SHA-512 等;IAM 用户凭据「Valid up to 7 days when you’re using AWS Signature Version 4」;临时凭据「Can’t be valid for longer than the credentials themselves」,角色会话到期即失效、EC2 实例角色「typically 6 hours」;凭据被吊销、删除、停用时 URL 失效,「even if the URL was created with a later expiration time」;控制台 1 分钟–12 小时、CLI/SDK 最高 7 天;「Amazon S3 checks the expiration date and time of a signed URL at the time of the HTTP request」,下载中途过期不影响本次、断线后重试失败;能力受生成者权限限制、「presigned URLs are bearer tokens」;
s3:signatureAge示例策略(600000,10 分钟);aws:SourceIp/aws:SourceVpc/aws:SourceVpce网络路径限制;FAQ 的「whichever occurs first」、ECS 角色 1–6 小时轮换、AssumeRole默认 1 小时、EC2 元数据凭据最长约 6 小时;403 的权限解释;HeadersNotSigned: if-range与If-Range签名要求;SignatureDoesNotMatch的时钟/代理/参数完全一致三条;ExpiredToken表示凭据已失效。 - Sharing objects with presigned URLs核查于 2026/10/30(200,27 341 B):默认所有对象私有,对象所有者可用预签名 URL 分享;控制台可分享最大 5 TB、最长期限 12 小时;CLI 最长 7 天、
--expires-in 604800示例;2019-03-20 之后上线的区域需指定--endpoint-url与--region;SDK 的Tagging属性必须以请求头而非查询参数传递。 - Uploading objects with presigned URLs核查于 2026/10/30(200,23 924 B):上传 URL 受生成者权限限制;同 key 会替换已有对象、上传后桶所有者拥有对象;上传文件必须与 key 完全一致;
curl -X PUT -T与 URL 加引号;Python 示例的ExpiresIn为 1000 秒(示例值)、ContentType参数;「确保上传请求的 content type 与生成 URL 时指定的相同」;SignatureDoesNotMatch六条排查:校时、URL 原样使用、检查过期、Content-Type一致、区域正确、curl加引号。