华为云 OBS 预签名 URL 实战:让前端安全直传文件,告别中转服务器
一次真实的踩坑复盘:用 OBS 预签名 URL 打通「前端 → 对象存储」直传链路,把带宽和 CPU 从业务服务器上彻底卸载下来。
一、一个被忽视的带宽黑洞
做过文件上传的开发者大概率都写过这样的接口:前端把文件 POST 给业务服务器,服务器再调用 SDK 转存到对象存储。功能没问题,但隐患很明显——所有流量都要经过业务服务器中转,一个 100MB 的视频就能吃掉可观的带宽和内存;并发一高,服务器先撑不住。更要命的是,文件在上传途中会完整地在应用内存里走一遍,大文件直接把进程顶到 OOM。
华为云 OBS 提供的**预签名 URL(Pre-signed URL)**就是为解决这个问题而生的:让浏览器直接与 OBS 交互,业务服务器只负责「签发通行证」,不碰文件本体。
二、原理:一句话说清
预签名 URL 本质是把访问密钥(AK/SK)的签名结果编码进 URL 的查询参数里。客户端拿着这个带签名的 URL,就等价于获得了「在指定时间、对指定对象、执行指定操作」的临时授权。授权粒度精确到 Method + Bucket + Object + 过期时间,过期后自动失效,天然适合临时下发。
签名在服务端完成,AK/SK 永远不出内网,这是它比「把密钥塞进前端」安全得多的根本原因。
三、服务端签发(Python)
以官方 esdk-obs-python 为例,签发一个上传(PUT)用的预签名 URL 只需要几行:
from obs import ObsClient
client = ObsClient(
access_key_id=os.getenv("HUAWEICLOUD_SDK_AK"),
secret_access_key=os.getenv("HUAWEICLOUD_SDK_SK"),
server="https://obs.cn-north-4.myhuaweicloud.com", # 与桶所在区域一致
)
def sign_upload(bucket: str, key: str, expires: int = 3600) -> str:
resp = client.createSignedUrl(
method="PUT",
bucketName=bucket,
objectKey=key,
expires=expires, # 单位:秒
)
return resp.signedUrl
密钥务必通过环境变量或 IAM 委托注入,绝不写死在代码或配置文件里。
四、前端上传
签名只需一次,前端拿到 URL 后直接用 fetch PUT 即可:
const { url } = await (await fetch(`/api/obs/sign?key=avatar/1.png`)).json();
await fetch(url, {
method: "PUT",
body: file,
headers: { "Content-Type": file.type },
});
至此,文件流量全部走 OBS,业务服务器只管签发,链路清爽。
五、实战避坑清单
- 签名头必须一致:如果调用
createSignedUrl时传了Headers参数,客户端请求就必须带上完全相同的头,否则签名校验失败返回 403。最省心的做法是生成时不加Headers,仅保留Content-Type。 - endpoint 要选对:预签名 URL 的域名取的是
server参数。用内网 endpoint 签出来的 URL,公网浏览器是打不开的,反之亦然。按调用方实际网络环境选择。 - 过期时间是双刃剑:
expires太短,大文件传到一半就失效;太长又扩大了泄露窗口。建议按业务设 5–30 分钟,并配合「上传完成后再由后端校验」的双保险。 - 别忘了桶策略:预签名放行的是「操作」,桶本身仍需允许对应账号执行该操作。若报 403,先查桶策略(Bucket Policy)和 IAM 权限,再怀疑签名。
- 时钟偏移:预签名依赖时间戳,客户端与 OBS 服务端时间偏差过大也会校验失败,容器环境记得开启 NTP 同步。
六、小结
预签名 URL 用「服务端签名、客户端直传」的模式,一次性解决了带宽中转、内存压力和密钥泄露三个问题,是 OBS 上一个性价比极高的能力。它的核心心法只有一句:授权在服务端签发,流量在客户端流转,密钥永不落地前端。掌握这三点,你也能把文件上传做成一个「零中转」的轻盈链路。
- 点赞
- 收藏
- 关注作者
评论(0)