前端直传OSS服务端签名(Policy+Signature)/STS临时凭证

前端直传OSS服务端签名(Policy+Signature)/STS临时凭证 阿里云OSS后端(你的服务器)前端(浏览器/App)阿里云OSS后端(你的服务器)前端(浏览器/App)1. 获取临时签名2. 文件直传alt[配置了上传回调][未配置回调]请求获取上传签名/凭证生成Policy和Signature或获取STS临时凭证返回签名信息(AccessKeyId, Signature, etc.)携带签名信息直接上传文件返回文件信息(ETag, 地址等)文件上传完成后POST请求回调地址返回回调结果前端主动将文件信息发送给后端返回最终处理结果⚙️ 后端必须执行的三个核心任务无论你使用的是阿里云OSS、腾讯云COS还是AWS S3以下三个步骤是后端在“前端直传”方案中必须承担的核心职责配置OSS环境为后续的代码集成做好准备。创建存储空间Bucket并设置访问权限通常权限建议设置为“公共读”即文件上传需要签名但读取链接可以公开。严禁设置为“公共读写”否则任何知道你Bucket地址的人都可以任意上传或删除文件。配置跨域规则CORS必须为你的Bucket开启CORS允许你的前端域名跨域访问OSS。在规则中可以临时将“来源”设置为*允许所有来源但在生产环境务必将其替换为你的具体前端域名以提升安全性。颁发临时上传凭证这是后端最核心的职责目前有两种主流方式各有优劣。3.实现业务校验与控制在生成凭证前后端应进行必要的业务逻辑校验。身份验证先验证当前用户是否已登录并确认其上传权限。文件限制根据业务场景对允许上传的文件类型、大小、数量、用户每日配额等进行限制。路径管理为文件指定存储路径建议为每个用户生成独立的目录如/uploads/user_{id}/避免文件被覆盖或越权访问。 上传完成后的回调处理文件成功上传到OSS后你的应用还需要知道这件事。OSS提供了两种方式来通知你的后端方式一配置上传回调这是推荐的后端获取通知的方式。前端发起上传请求时将你后端的回调地址一并发给OSS。OSS保存文件后会主动POST请求你配置的回调地址传递文件信息你可以据此更新数据库。方式二前端主动上报如果不使用回调OSS会将上传结果直接返回给前端。你可以让前端拿到结果后再调用一个你自己的后端接口来上报信息。✅ 方案优劣与实施检查清单优势后端完全解放极大的减轻了服务器带宽和计算压力。同时由于采用签名或临时凭证避免了前端泄露密钥的风险安全性远高于前端直传。劣势实现相对复杂需要设计签名服务并处理好前端、后端与OSS三方之间的交互逻辑。实施检查清单环境配置是否创建了“公共读”的Bucket并配置了CORS规则安全设计后端是否在使用STS临时凭证而不是直接暴露长期密钥业务控制后端在生成凭证前是否对用户身份和上传限制做了充分校验数据闭环是否配置了OSS上传回调或设计了前端上报机制以确保文件信息被后端记录MD5 校验的具体方案三种主流方式对比阿里云OSS主要提供了MD5和CRC64两种校验方式以下是它们的对比校验方式执行方适用场景优点缺点MD5客户端计算OSS服务端校验对数据一致性有严格要求的场景如金融、医疗、档案系统客户端可提前知道文件MD5实现秒传、去重等功能校验严格手动实现有一定复杂度对性能有一定影响CRC64OSS服务端计算客户端可选校验默认开启适用于对性能要求较高的通用场景对性能影响小由OSS SDK默认处理无需额外开发客户端需在下载时自行校验无法主动上传前校验不校验无不推荐用于生产环境仅适用于测试最简单无开发量和性能损耗无法保证数据完整性存在静默损坏风险️ 如何在前端直传中实施MD5校验阿里云OSS你的后端服务前端(浏览器)阿里云OSS你的后端服务前端(浏览器)alt[校验不一致][校验一致]1. 计算文件的MD5值2. 请求上传凭证3. 业务验证4. 返回临时凭证(含Policy)5. 发起POST直传 (Header带Content-MD5)6. 对比MD5返回错误 (InvalidDigest)返回成功 (含ETag)7. 上报文件信息 (含服务端返回的ETag)8. 二次校验并更新数据库具体到代码层面这里有一些关键的实现要点前端计算与传递MD5前端计算你需要在客户端浏览器读取用户选择的文件并计算其MD5值。标准方法使用FileReader或Blob.slice()读取文件流再用SubtleCrypto.digest()或SparkMD5等纯JS库计算。为避免大文件上传时的卡死建议使用Web Worker在后台线程计算。示例代码 (使用Web Crypto API)javascriptasync function computeMD5(file) { const buffer await file.arrayBuffer(); const hashBuffer await crypto.subtle.digest(MD5, buffer); const hashArray Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b b.toString(16).padStart(2, 0)).join(); } // 将计算出的MD516进制传入后续请求中添加到请求将计算出的MD5值作为Content-MD5头添加到上传请求中。编码要求注意Content-MD5头部要求的值是文件的MD5原始二进制数据再进行Base64编码的结果而不是你计算出的32位十六进制字符串。示例代码 (转换)javascript// md5Hex: 从上面获取的32位十六进制字符串 function hexToBase64(hexStr) { const bytes new Uint8Array(hexStr.match(/.{1,2}/g).map(byte parseInt(byte, 16))); return btoa(String.fromCharCode.apply(null, bytes)); } // 实际使用 const contentMd5Base64 hexToBase64(computedMD5Hex);预签名方式如果你使用的是服务端生成的预签名URL来上传OSS也支持MD5校验。需要确保你的预签名策略中包含了Content-MD5头部这样前端在上传时必须携带正确的MD5值才能成功后端校验MD5校验是一个需要前后端协同完成的工作后端在此扮演着双重角色策略制定与凭证下发在生成上传凭证如STS Token时后端应在Policy中声明要求MD5校验。这是确保校验流程被强制执行的关键一步。Policy关键条件json{ conditions: [ [eq, $content-md5, 待校验的Content-MD5值] ] }这样做可以让OSS服务端在接收文件时强制要求请求头中携带的Content-MD5值与您计算的一致。二次验证这是防范中间环节篡改的最后一道防线。在接收到前端的上传成功通知后后端不应盲目信任而应主动校验。结果对比OSS上传成功后返回的ETag对于简单上传即文件的MD5值与前端计算并上报的MD5进行比对。主动校验对于非简单上传如分片上传或需要更高安全等级的文件后端可以主动通过HeadObject接口获取文件的ETag或其他元信息进行比对。 实施建议小文件 vs 大文件对于小文件 100 MB直接计算并校验MD5开销不大推荐使用。对于大文件可以考虑两种方案分段计算前端在选取文件后通过Blob.slice()分块读取并逐步更新MD5避免内存溢出。高性能库在大文件场景下可以使用FlashMD5这类高性能库来提升计算速度。分片上传如果你实现了分片上传阿里云支持在每个分片上传时分别进行MD5校验。每个分片上传成功后需要保存其ETag即该分片的MD5值最后在“完成分片上传”时将所有分片的ETag列表提交给OSS以组装成完整文件。 常见误区区分ETag与MD5在实际开发中一个常见的误区是将OSS返回的ETag直接当作文件的完整MD5来使用。虽然对于简单的PutObject上传ETag确实是文件的MD5值。但对于以下情况ETag不等于MD5分片上传通过UploadPart和CompleteMultipartUpload上传的文件其最终ETag是根据所有分片的MD5组合计算出的一个特殊值不是整个文件的MD5。其他方式创建通过追加上传Append、拷贝Copy等方式创建的文件ETag也不是文件的MD5。安全说明即使ETag是MD5阿里云官方文档也不建议将其作为校验数据一致性的唯一依据。因此最严谨的做法是主动将Content-MD5头随文件上传让OSS完成校验这是官方推荐的保障数据一致性的方法。✅ 三种方案的对比与建议方案做法是否推荐理由1. 不做任何主动 MD5 校验仅依赖 OSS 默认的CRC64校验SDK 或服务端自动完成。✅ 推荐最常见头像损坏概率极低CRC64 已能捕获绝大多数传输错误实现成本为零。即使极少数坏图出现用户反馈后再处理即可。2. 前端计算 MD5 并带上 Content-MD5 头前端计算 MD5 → 转 Base64 → 添加到请求头OSS 服务端校验。 可选增强可靠性多一层保障能防止极罕见的静默损坏且对小文件性能影响可忽略。但如果前端计算库有 bug 或浏览器支持问题反而可能增加失败率。3. 后端二次校验 ETag 或主动下载比对回调接口里拿到 OSS 返回的 ETag与前端上报的 MD5 比对或后端下载头像重新计算。❌ 不推荐头像场景下收益极低却增加了后端复杂度和回调延迟。ETag 对于普通上传等于 MD5再比对意义不大下载重算则浪费带宽和 CPU。后端区分业务类型后端区分业务类型的关键在于前端在发起上传请求时通过“自定义参数”把业务场景告诉 OSSOSS 回调时再把这些参数透传给后端。至于数据库操作强烈建议在回调接口中直接完成而不是让前端二次调用。原因避免数据不一致OSS 回调是文件上传成功的权威确认。如果等前端再调接口网络异常或用户关闭页面会导致回调成功但数据库没记录。保持原子性文件上传和数据库记录应该是事务性的。回调里完成业务逻辑最合理。减少前端负担前端不用等待上传后再调一次接口逻辑更简单。️ 方案通过callback-var自定义参数区分业务阿里云 OSS 支持在直传时添加自定义回调参数callback-var这些参数会随回调请求一起发送到你的后端。步骤 1前端在表单中添加自定义变量前端使用 OSS 的multipart/form-data表单上传时除了必填的key、policy、OSSAccessKeyId、signature等可以追加以x:开头的自定义变量javascriptconst formData new FormData(); // OSS 必填字段 formData.append(OSSAccessKeyId, sts.accessKeyId); formData.append(policy, policy); formData.append(signature, signature); formData.append(key, avatar/user123/head.jpg); formData.append(file, file); // 自定义业务变量注意 key 必须带 x: 前缀 formData.append(x:biz_type, avatar); // 业务类型 formData.append(x:user_id, 123); // 用户ID formData.append(x:filename, head.jpg); // 原始文件名 // 同时需要在 policy 的 conditions 中允许这些字段否则签名校验会失败步骤 2后端生成 Policy 时声明允许的x:字段后端在生成policy时需要在conditions数组中包含这些字段json{ conditions: [ [starts-with, $key, avatar/], [eq, $x:biz_type, avatar], [eq, $x:user_id, 123] ] }步骤 3配置回调时要求携带自定义变量后端生成callback参数时可以这样设置通常是 JSON 字符串json{ callbackUrl: https://your-api.com/api/oss/callback, callbackBody: bucket${bucket}object${object}etag${etag}size${size}biz_type${x:biz_type}user_id${x:user_id}filename${x:filename}, callbackBodyType: application/x-www-form-urlencoded }步骤 4后端回调接口处理在 Flask或其他框架的回调视图里根据biz_type参数即可区分业务pythonbp.route(/api/oss/callback, methods[POST]) def oss_callback(): # 验证签名必须 # 获取参数 biz_type request.form.get(biz_type) user_id request.form.get(user_id) object_key request.form.get(object) etag request.form.get(etag) size request.form.get(size) if biz_type avatar: # 直接更新用户表的 avatar_url 字段 user User.query.get(user_id) if user: user.avatar_url fhttps://your-bucket.oss-cn-shenzhen.aliyuncs.com/{object_key} user.avatar_etag etag db.session.commit() # 可选删除旧头像文件异步 elif biz_type article_image: # 保存到图片表暂不关联文章等前端提交表单时再关联 img ArticleImage( user_iduser_id, urlfhttps://.../{object_key}, etagetag, statusuploaded ) db.session.add(img) db.session.commit() # 返回图片 ID 给前端OSS 回调响应会被前端接收 else: return jsonify({code: 400, msg: unknown biz_type}), 400 return jsonify({code: 0, msg: ok}), 200 特殊情况无法透传自定义参数时如纯 GET 预签名如果你使用的是预签名 URL方式而不是 POST 表单x:参数无法通过 URL 传递。此时可以这样区分不同业务用不同的回调地址例如头像回调/api/oss/callback/avatar文章配图回调/api/oss/callback/article。后端生成预签名 URL 时就已经决定了回调地址。通过 object key 前缀区分强制要求头像上传的 key 必须以avatar/开头文章配图以article/开头。回调里解析object字段根据前缀判断业务。 关于“回调直接保存 vs 前端二次调用”场景回调直接保存前端二次调用头像上传✅推荐回调里直接更新用户表的头像字段原子性最好。❌ 不好用户上传完头像还得再调接口多了失败风险。文章配图先上传图片最后提交文章✅ 回调里创建图片记录状态为“未关联”前端提交文章时传入图片 ID 完成关联。 也可以前端拿到上传成功的 URL最后一起提交。但会丢失 ETag、大小等元信息且无法防止 URL 被篡改。临时文件如导入 Excel 后立即处理回调里触发异步任务如发消息队列不用等待前端。❌ 不推荐前端不知道处理进度还得轮询。结论除非有特殊的“先上传再确认关联”需求否则一律在回调中完成数据库写入或触发后续任务。前端二次调用仅用于“关联多对多关系”这种回调时无法确定关联 ID 的情况。✅ 总结区分业务类型通过 OSS 自定义回调参数x:biz_type或 key 前缀。数据库写入直接在回调接口里完成不要依赖前端二次调用除了需要前端提供额外业务 ID 的情况。安全性回调接口必须验证 OSS 签名防止伪造请求。头像场景典型做法前端上传时携带x:biz_typeavatar和x:user_id回调里直接更新User.avatar_url。