前端不能直接调用云存储put接口,因签名密钥(如accesskeysecret)绝不可暴露在客户端,否则导致凭证泄露、未授权上传等安全风险;必须由后端生成有时效、有路径前缀限制、最小权限的预签名url供前端上传。

为什么不能让前端直接调用云存储的 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 告警强。