项目在使用aws的s3对象存储服务,需要后端将请求预签名后传给前端,前端拿这些签名信息跟s3交互。由于我没搞过s3的签名,需要花时间去调研官方的sdk等资料,花了一天时间终于搞出来了,碰了很多坑,在这里记录一下。
小知识,大挑战!本文正在参与“
程序员必备小知识
”创作活动
不要自己做签名
首先,AWS提供了签名的步骤和签名的组成信息,很多人以为不难,想要自己实现。其实非常不推荐,因为他们的文档只是做参考,让读者对签名的原理有个大体的认知,并不是严格的签名教程。
如果你真的要这么做,会发现出错率非常高,最常见的是签名不一致,你提供的签名跟AWS根据你负载计算而得到的签名不匹配,并且AWS没有帮你定位出错点,他只告诉你你错了。最终的结果就是你排查了半天也找不到原因所在。我就是在这里浪费了太多时间。
不过坑只有踩了你才知道这是坑,这也是一种收获,不是吗?
推荐用sdk来做签名,轻松又简单!
用SDK做预签名
首先需要安装一些依赖包,这里采用本文发稿时的最新版
aws-sdk
:
@aws-sdk/client-s3
@aws-sdk/s3-request-presigner
第一个是s3的客户端sdk,第二个是预签名请求的sdk
首先做s3客户端初始化工作:
const {
S3Client
} = require("@aws-sdk/client-s3");
const REGION = "****";
const s3Client = new S3Client({
region: REGION,
credentials: {
accessKeyId: "******",
secretAccessKey: "**************",
单文件上传签名示例
写一个/upload-info的端点给前端做签名调用
const {
PutObjectCommand,
} = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
app.get("/upload-info", (req, res, next) => {
const putCmd = new PutObjectCommand({
Bucket: "****",
Key: "image.jpg"
getSignedUrl(s3Client, putCmd, { expiresIn: 3600 }).then((url) => {
res.send(url);
next();
前端获取并使用签名请求
request({
url: `http://localhost:8080/upload-info`,
}).then((signedUrl) => {
request({
url: signedUrl,
method: "put",
data: file,
}).then((res) => {
console.log(`single file upload succeed!`);
分段上传签名示例
写一个/upload-part
端点给前端做签名调用
const {
CreateMultipartUploadCommand,
CompleteMultipartUploadCommand,
UploadPartCommand,
} = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
app.get("/upload-part", (req, res, next) => {
const Key = 'book.pdf'
const length = req.query.count;
const createMultiUpload = s3Client.send(
new CreateMultipartUploadCommand({
Bucket: "***",
Key,
const getCompleteUrl = (UploadId) => {
return getSignedUrl(
s3Client,
new CompleteMultipartUploadCommand({
Bucket: "***",
Key,
UploadId,
{ expiresIn: 3600 }
const prePromise = createMultiUpload.then((result) => {
const { Key, UploadId } = result;
return getCompleteUrl(UploadId).then((completeUrl) => {
return {
Key,
UploadId,
CompleteUrl: completeUrl,
prePromise.then((result) => {
const { Key, UploadId, CompleteUrl } = result;
const signPartPromsArr = [];
for (let PartNumber = 1; PartNumber <= length; PartNumber++) {
const cmd = new UploadPartCommand({
Bucket: "***",
Key,
PartNumber,
UploadId,
signPartPromsArr.push(getSignedUrl(s3Client, cmd, { expiresIn: 3600 }));
Promise.all(signPartPromsArr).then((partsUrlArr) => {
res.send({
partEndpoints: partsUrlArr,
CompleteUrl,
Key,
UploadId,
next();
所以可以看到,整个签名包括三部分:
通过sdk创建分段上传,获取上传Id
根据桶、对象的Key、分段编号上传Id签名每一个分段端点
根据上传Id签名完成分段上传的端点(通知s3进行分段合并)
其中前段获得的两种签名:
每一个分段的签名端点
完成分段上传的签名端点
至于前端的使用示例不再赘述,跟单文件使用本质一致的,只是细节多一点:
最小切片size不可小于5MB(s3的硬性规定)
并发数量的控制
进度的提示
所有分片上传完毕,需要将每段的编号和Etag
(从responseHeader
获取),按照从小到大的顺序拼成一个XML文本通过另一个签名端点(完成分段上传端点)发送给S3
感谢阅读,如有任何问题,欢迎留言讨论!