允许用户将内容上传到s3

时间:2017-04-03 01:34:01

标签: javascript node.js amazon-web-services express amazon-s3

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

基于对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://stackoverflow.com/a/28699269/251162。)

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

5 个答案:

答案 0 :(得分:5)

我认为在直接发布到S3的情况下,跳过后端服务器可能会更好。

您可以做的是定义一个策略,明确指定可以上传到哪里的内容,然后使用AWS秘密访问密钥对该策略进行签名(使用AWS sig v4,可以使用{{3}生成策略})。

如果在this

中可见,则使用策略和签名的示例

根据您的用途,您可以指定以下条件:

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`);

答案 1 :(得分:3)

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

  

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

在此处阅读更多内容:Cognito identity pools

创建新的识别池后,您可以在使用S3 JavaScript SDK时引用它,这样您就可以上传内容,而不会向客户端公开任何凭据。

此处示例:Uploading to S3

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

谜题的第二部分 - 验证。

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

答案 2 :(得分:2)

我知道这是我们项目中的内容,因此我将向您展示部分代码:

您首先需要发布到您自己的服务器以获取上传的信用, 从那里你将把客户端上传的params返回给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);

您从后端获得的所有凭据项目。

答案 3 :(得分:2)

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

请参阅CLI docsSDK docs以及more SDK

直接生成链接时不支持内容大小限制。但是链接就是用来转发AWS用户拥有的访问权限。

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

答案 4 :(得分:2)

您的服务器就像代理一样,也负责授权,验证等。 一些代码段:

 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);
}

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