私有桶的签名 URL 怎么发
本文按私有桶写(合同、图纸、人脸照片没有一样能公开读;公共读的图片桶是另一套思路,不在这一篇里),桶一旦私有,前端就不能直接拿对象地址了。解法是预签名 URL:服务端用自己的凭证对「某个对象、某个方法、某个截止时间」做一次签名,拼成一条 URL 发给前端,前端在有效期内拿它直接访问对象存储,不经过应用服务器。
生成一条预签名 URL,被签的内容固定四样,外加一个签名者:
| 要素 | 内容 | 签完能不能改 |
|---|---|---|
| bucket | 哪个桶 | 不能 |
| object key | 哪个对象的完整路径 | 不能,连前缀都不能通配 |
| 方法 | GET 下载 / PUT 上传 / HEAD 读元数据 | 不能,GET 签出来的拿去 PUT 是 403 |
| 过期时间 | 绝对时间点 | 不能 |
| 签名者 | 签发方的 AK/SK 或临时凭证(STS 签发时,临时 token 本身也在 URL 参数里,一样要当秘密看) | — |
Java SDK 里生成一条下载链接是这样(阿里云 OSS;S3 的 AWS SDK v2 用 S3Presigner,形态不同思路一样):
import com.aliyun.oss.HttpMethod; // 别导成 Spring 的 HttpMethod
import com.aliyun.oss.model.*;
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
conf.setSignatureVersion(SignVersion.V4); // 显式 V4;默认 V1 已停维护
OSS ossClient = new OSSClientBuilder().build(endpoint, ak, sk, conf);
Date expiration = new Date(System.currentTimeMillis() + 10 * 60 * 1000);
GeneratePresignedUrlRequest req =
new GeneratePresignedUrlRequest(bucket, objectKey, HttpMethod.GET);
req.setExpiration(expiration);
URL url = ossClient.generatePresignedUrl(req);
objectKey 是服务端查附件表拿到的完整路径,不接前端传来的路径——前端传路径等于让它自己决定能签哪个对象,租户隔离就没了。上传和下载都一样:key 永远由服务端决定。
三条注意,前两条和直觉相反。
有效期不是你写多长就多长。官方文档的规则:用长期凭证(控制台建的 AK/SK)签,最长 7 天;用临时凭证(STS 是云厂商的临时授权服务,发出来的凭证几分钟到几小时就过期;实例角色是云主机自动挂的同类凭证)签,凭证到期 URL 就失效,哪怕 URL 自己的过期时间还没到。云上的应用常常跑在角色凭证下:STS AssumeRole 的 DurationSeconds、角色的 MaxSessionDuration、ECS 实例角色的元数据刷新周期,都决定凭证活多久,常见值在一到六小时。签一条「24 小时有效」的下载链接发给客户,中午凭证轮换了,链接就 403。要发长链接,签发那一步要用长期凭证,或者干脆不发长链接,让前端每次要下载时实时要一条 10 分钟的。
URL 本身就是权限。签名 URL 是 bearer token,谁拿到谁能用,不看是谁。所以它不能进日志——Nginx 的 access log 默认记完整 query string,签名参数就在里面;应用日志打印请求参数、前端埋点上报当前 URL,都是泄露面。处理方式是把签名 URL 当密码级字段:Nginx 的 log_format 里用 $uri 替掉记完整 query 的 $request,应用日志打印参数前把 Signature、x-oss-signature、X-Amz-Signature、security-token 这几个参数名遮掉。有效期短也是同一个目的:泄了也只能用几分钟。
上传用的 PUT 签名会覆盖。同一个 object key 签了 PUT,拿着它传两次,第二次直接覆盖第一次,对象存储不报错。所以上传的 key 要服务端生成且不可预测(UUID 或时间加随机),一个 key 只签一次。存储层还有一道现成的闸:阿里云的 PutObject 和 PostObject 支持 x-oss-forbid-overwrite: true,把 header 签进预签名 URL,重复写入直接 409 FileAlreadyExists(S3 对应 If-None-Match: *,返回 412);业务表里标记 uploaded 是第二道。给第三方系统发上传链接时,key 里带上业务单号和随机后缀,再把 callback 表单域一起签进 URL——第三方传完,OSS 会主动 POST 你指定的接口,链路才闭合;没有这一步,第三方传没传成功你要靠定时去桶里对账才知道。
系列:深文 文件上传:直传、断点续传、秒传各解决什么 | 相关 私有化部署的最小交付包 | 技术纵深系列索引