为什么不能让前端直接调用云存储的 PUT 接口
因为绝大多数云存储(如 AWS S3、阿里云 OSS、腾讯云 COS)的直传接口需要签名凭证,而签名密钥绝不能暴露在前端。一旦把
AccessKeySecret或临时 token 的签发逻辑放在客户端,等于把仓库钥匙焊死在门把手上。
常见错误现象:
SignatureDoesNotMatch、
InvalidAccessKeyId、甚至日志里突然出现大量来自境外 IP 的上传请求——那不是攻击,是你的前端代码把
SecretKey拼进了 URL 或 header 里,被爬虫/审查工具一眼捕获。
正确路径只有一条:客户端先向你自己的后端申请一个「有时效、有权限、有前缀限制」的临时上传凭证,再用它跟云存储打交道。
如何用 C# 后端生成带签名的预签名 URL(S3/OSS/COS 通用思路)
核心不是“生成 URL”,而是控制三件事:过期时间、目标路径前缀、最小必要权限。C# 里别手写 HMAC-SHA256 签名,直接用官方 SDK。
AWS S3:用AmazonS3Client.GeneratePresignedUrl,必须指定
HttpMethod.PUT和
expiresIn(建议 ≤ 15 分钟) 阿里云 OSS:用
OssClient.GeneratePresignedUrl,注意设置
Expiration和
HttpVerb,且
bucketName和
objectName要严格匹配前端将要上传的实际值 腾讯云 COS:用
CosXmlServer.GetPresignedURL,需传入
method、
path、
expires,且 path 必须以
/开头
关键细节:预签名 URL 里的
objectName最好由后端生成(比如
uploads/{userId}/{timestamp}_{random}.jpg),而不是让前端自由填写。否则可能被恶意覆盖已有文件或遍历目录。
前端拿到预签名 URL 后怎么传(不走 FormData,用 fetch + Blob)
FormData 会触发 multipart/form-data 编码,而预签名 URL 默认只接受原始二进制流(raw body)。直接
fetch(url, { method: 'PUT', body: file }) 就行,别包一层 new FormData()。
容易踩的坑:
没设Content-Typeheader:S3/OSS 会按 extension 猜类型,但 COS 强制要求 header 里声明,否则 403;建议统一设为
file.type || 'application/octet-stream'跨域问题:确保云存储 bucket 的 CORS 配置允许你的前端域名,并显式放开
PUT方法和
Content-Typeheader 大文件没分片:单个预签名 URL 通常只支持 ≤ 5GB;超限得走分片上传流程,此时后端返回的就不是 URL,而是 uploadId + 多个分片签名
示例片段(前端):
fetch(presignedUrl, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file
})临时凭证服务必须加哪些防护(绕过就等于裸奔)
这个接口本身比上传更危险——它是整个链路的单点故障和攻击入口。C# 后端至少做三件事:
必须校验调用方身份:JWT 或 session,不能靠 query string 传userId必须限制单用户频次:比如 1 分钟最多申请 5 次,用
IDistributedCache记录 key 为
upload_quota:{userId}
必须绑定文件元信息:前端传来的 filename、
size、
contentType全部要在后端校验,比如拒绝
.exe后缀、超 10MB 的图片、
text/html类型的“图片”
最常被忽略的一点:预签名 URL 生成时,
objectName里不要拼接用户可控的完整路径(如
../etc/passwd),要用白名单字符过滤或哈希重命名。哪怕只是防御性编程,也比等 SOC 告警强。
