允许用户将内容上传到 s3

Posted

技术标签:

【中文标题】允许用户将内容上传到 s3【英文标题】:Allowing users to upload content to s3 【发布时间】:2017-08-27 18:32:24 【问题描述】:

我在区域 BUCKET_REGION 上有一个名为 BUCKET 的 S3 存储桶。我正在尝试允许我的网络和移动应用程序的用户将图像文件上传到这些存储桶,前提是他们满足基于 Content-TypeContent-Length 的某些限制(即,我只想允许小于 3mbs 的 jpeg上传)。上传后,文件应该可以公开访问。

基于对 AWS 文档的大量挖掘,我假设该过程在我的前端应用程序上应该如下所示:

const a = await axios.post('my-api.com/get_s3_id');

const b = await axios.put(`https://BUCKET.amazonaws.com/a.id`, 
   // ??
   headersForAuth: a.headersFromAuth,
   file: myFileFromSomewhere // i.e. html5 File() object
);

// now can do things like <img src=`https://BUCKET.amazonaws.com/a.id` />
// UNLESS the file is over 3mb or not an image/jpeg, in which case I want it to be throwing errors

在我的后端 API 上我会做类似的事情

import aws from 'aws-sdk';
import uuid from 'uuid';
app.post('/get_s3_id', (req, res, next) => 
  // do some validation of request (i.e. checking user Ids)
  const s3 = new aws.S3(region: BUCKET_REGION);
  const id = uuid.v4();
  // TODO do something with s3 to make it possible for anyone to upload pictures under 3mbs that have the s3 key === id
  res.json(id, additionalAWSHeaders);
);

我不确定我应该查看哪些确切的 S3 方法。


以下是一些不起作用的事情:

我看到很多提到(一个非常古老的)API 可通过s3.getSignedUrl('putObject', ...) 访问。但是,这似乎不再支持可靠地设置ContentLength——至少不再支持。 (见https://***.com/a/28699269/251162。)

我还看到了一个使用HTTP POSTform-data API 的更接近工作的示例,该API 也很旧。我想如果没有其他选择,这可能会完成,但我担心它不再是“正确”的做事方式——此外,它似乎做了很多手动加密等而不使用官方节点开发工具包。 (见https://***.com/a/28638155/251162。)

【问题讨论】:

您能否确保您的 S3 存储桶附加了正确的策略。它需要公开。 【参考方案1】:

这是我知道我们的项目中有的东西,所以我将向您展示部分代码:

您首先需要发布到您自己的服务器以获得上传的凭据, 从那里您将返回从客户端上传到 S3 的参数。

这些是您发送到 aws s3 服务的参数,您将需要存储桶、上传路径和文件

 let params = 
        Bucket: s3_bucket,
        Key: upload_path,
        Body: file_itself
    ;

这是我实际上传到 s3 的代码

config.credentials = new AWS.Credentials(credentials.accessKeyId, 
credentials.secretAccessKey, credentials.sessionToken);
    let s3 = new S3(config);
    return s3.upload(params, options).on("httpUploadProgress", handleProgress);

当然,您从后端获得的所有这些凭据项。

【讨论】:

谢谢,但我不想向客户发送通用秘密 AWS 密钥(即使是有限的 IAM 角色)!您是否在某处生成了特定于密钥的密钥? 我知道它不是 const 键,它是为每个上传请求生成的,所以不用担心它被劫持,因为它很快就会死 你是如何生成密钥的?以及如何确保用户不会上传大于 3MB 的文件?【参考方案2】:

在后端,您需要生成一个定时、预签名的 URL,并将该 URL 发送到客户端以访问 S3 对象。根据您的后端实施技术,您可以使用 AWS CLI 或 SDK(例如用于 Java、.Net、Ruby 或 Go)。

请参考CLI docs和SDK docs和more SDK

链接生成不直接支持内容大小限制。但是该链接只是为了传递 AWS 用户拥有的访问权限。

要在上传时使用策略限制文件大小,您必须在存储桶上创建 CORS 策略并使用 HTTP POST 进行上传。请参阅this link。

【讨论】:

正如我在问题中提到的,至少在 Node API 中,据我浏览其他 API 可以看出,s3.getSignedUrl() 函数不允许将内容大小限制为被设置。如果您可以提供一个签署内容长度受限网址的示例,我们会很高兴在这里出错。 是的,链接生成中不直接支持内容大小限制。但该链接只是为了传递 AWS 用户拥有的访问权限。 您能否描述一下使用后端的 Node SDK 和前端的现代 AJAX 库来实现 POST 策略等的“正确”方式?【参考方案3】:

您需要熟悉 Amazon Cognito,尤其是身份池。

使用 Amazon Cognito Sync,您可以跨客户端平台、设备和操作系统检索数据,因此,如果用户开始在手机上使用您的应用程序,然后又切换到平板电脑,则保留的应用程序信息仍可用于那个用户。

在此处阅读更多信息:Cognito identity pools

创建新的身份池后,您可以在使用 S3 javascript SDK 时引用它,这将允许您上传内容而不会向客户端公开任何凭据。

此处示例:Uploading to S3

请通读所有内容,尤其是“配置 SDK”部分。

谜题的第二部分 - 验证。

我会着手实施客户端验证(如果可能的话),以避免在出现错误之前出现网络延迟。如果您选择在 S3 或 AWS Lambda 上实施验证,您正在寻找等待文件到达 AWS 的等待时间 - 网络延迟。

【讨论】:

验证当然需要在客户端进行,但也需要在服务器端进行以确保安全。 Aaron,当您上传到 S3 时,您唯一的选择是 AWS Lambda,否则您需要实施另一个后端层。您可以为您的存储桶指定 AWS Lambda,并让回调检查大小和内容类型,以防出现错误 - 删除对象并反映在前端。对 Lambda 使用 NodeJS,它内置了对 ImageMagick 的支持。 ImageMagick 的“识别”方法将提供所有需要的信息。 是否有理由更喜欢 Cognito + Lambda 进行验证而不是 a3.getSignedUrl() 和发布策略? @AaronYodaiken Cognito 是一项与a3.getSignedUrl() 功能相对的服务,它打开了许多其他功能。如果您的需求增长,Cognito 将允许您以敏捷的方式对市场做出反应。最好的办法是阅读FAQ 以了解潜力。值得一提的是,这会直接影响您的应用程序的当前状态:Amazon Cognito 与支持 SAML 或 OpenID Connect 的外部身份提供商、社交身份提供商(例如 Facebook、Twitter、Amazon)合作,您还可以集成自己的身份提供者。【参考方案4】:

您的服务器就像一个代理,还负责授权、验证等。 部分代码sn-p:

 upload(config, file, cb) 
  const fileType = // pass with request or generate or let empty
  const key = `$uuid.v4()$fileType`; // generate file name:
  const s3 = new AWS.S3();
  const s3Params = 
   Bucket: config.s3_bucket,
   Key: key,
   Body: file.buffer
 ;

 s3.putObject(s3Params, cb);

然后您可以将密钥发送给客户端并提供进一步的访问权限。

【讨论】:

这绝对是一种方法——所有间接的原因(在我的问题和其他答案中)是我们希望避免 S3 流量给应用程序服务器带来负担。 建议创建另一个负责处理推送/拉取/更新文件的服务。【参考方案5】:

我认为在这种情况下,直接 POST 到 S3 并跳过后端服务器可能会更好。

您可以做的是定义一个策略,明确指定可以上传到什么位置,然后使用 AWS 秘密访问密钥对该策略进行签名(使用 AWS sig v4,可以使用 this 生成策略) .

如果可以在AWS docs 中查看,策略和签名的示例用法

对于您的用途,您可以指定以下条件:

conditions: [
   ['content-length-range, 0, '3000000'],
   ['starts-with', '$Content-Type', 'image/']
]

这会将上传限制为 3Mb,Content-Type 仅限以 image/ 开头的项目

此外,您只需为策略生成一次签名(或每当它更改时),这意味着您无需向服务器请求即可获得有效的策略,您只需在 JS 中对其进行硬编码。何时/如果您需要更新,只需重新生成策略和签名,然后更新 JS 文件。

编辑:没有通过 SDK 的方法来执行此操作,因为它意味着直接从网页上的表单发布,即可以在没有 javascript 的情况下工作。

编辑 2:如何使用标准 NodeJS 包签署策略的完整示例:

import crypto from 'crypto';

const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
const ISO_DATE = '20190728T000000Z';
const DATE = '20161201';
const REGION = process.env.AWS_DEFAULT_REGION || 'eu-west-1';
const SERVICE = 's3';
const BUCKET = 'your_bucket';

if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) 
    throw new Error('AWS credentials are incorrect');


const hmac = (key, string, encoding) => 
    return crypto.createHmac("sha256", key).update(string, "utf8").digest(encoding);
;

const policy = 
    expiration: '2022-01-01T00:00:00Z',
    conditions: [
        
            bucket: BUCKET,
        ,
        ['starts-with', '$key', 'logs'],
        ['content-length-range', '0', '10485760'],
        
            'x-amz-date': ISO_DATE,
        ,
        
            'x-amz-algorithm': 'AWS4-HMAC-SHA256'
        ,
        
            'x-amz-credential': `$AWS_ACCESS_KEY_ID/$DATE/$REGION/$SERVICE/aws4_request`
        ,
        
            'acl': 'private'
        
    ]
;

function aws4_sign(secret, date, region, service, string_to_sign) 
    const date_key = hmac("AWS4" + secret, date);
    const region_key = hmac(date_key, region);
    const service_key = hmac(region_key, service);
    const signing_key = hmac(service_key, "aws4_request");
    const signature = hmac(signing_key, string_to_sign, "hex");

    return signature;


const b64 = new Buffer(JSON.stringify(policy)).toString('base64').toString();
console.log(`b64 policy: \n$b64`);
const signature = aws4_sign(AWS_SECRET_ACCESS_KEY, DATE, REGION, SERVICE, b64);
console.log(`signature: \n$signature\n`);

【讨论】:

嗨 Ryan,感谢您提供指向非官方发布策略 NPM 包的指针。您知道使用官方 AWS 节点 SDK 是否可以实现类似的操作?如果没有,您能否为赏金提供一个完整的工作示例? (在这一点上,我可以自己拼凑出如何做到这一点,但最好有一个单一的权威参考来说明如何为其他人做到这一点。) 问题是,你不需要使用官方的SDK来做,因为它是一个非常通用的过程(只是几个hmac签名组合)。您可以查看文档以了解他们建议如何进行签名 docs.aws.amazon.com/general/latest/gr/… 我添加了一个完整的代码示例,类似于我已经用来生成策略的代码。

以上是关于允许用户将内容上传到 s3的主要内容,如果未能解决你的问题,请参考以下文章

如何让用户直接把图片上传到对象存储 S3

s3直接上传限制文件大小和类型

将 base64 图像数据上传到 Amazon S3

上传到 S3 之前的 AWS 临时文件?

将 PDF 内容上传到 S3 存储桶

当用户从托管在 Digital Ocean 上的 Web 应用程序将图像上传到 AWS S3 时,是出站还是入站带宽传输?