允许用户将内容上传到s3

Aar*_*ken 6 javascript amazon-s3 amazon-web-services node.js express

我有一个以BUCKET区域命名的S3存储桶BUCKET_REGION.我试图允许我的网络和移动应用程序的用户将图像文件上传到这些存储桶,前提是它们满足基于Content-Type和的某些限制Content-Length(即,我希望仅允许上传小于3mbs的jpegs).上传后,文件应公开访问.

基于对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
Run Code Online (Sandbox Code Playgroud)

在我的后端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});
});
Run Code Online (Sandbox Code Playgroud)

我不确定的是我应该关注的S3方法.


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

  • 我已经看到很多(很老的)API可以访问s3.getSignedUrl('putObject', ...).但是,这似乎不支持可靠地设置ContentLength- 至少不再.(参见/sf/answers/2008948861/.)

  • 我也看到了使用一个近到工作示例HTTP POSTform-dataAPI,这也是很老了.我想如果没有其他选择,这可能会完成它,但我担心它不再是"正确"的做事方式 - 此外,它似乎做了很多手动加密等而不使用官方节点SDK.(参见/sf/answers/2004670881/.)

Rya*_*yan 5

I think what might be better for this case in POSTing directly to S3, skipping your backend server.

What you can do is define a policy that explicitly specifies what can be uploaded to and to where, this policy is then signed using an AWS secret access key (using the AWS sig v4, can generate a policy using this).

An example usage of the policy and signature if viewable in the AWS docs

For your uses you can specify conditions like:

conditions: [
   ['content-length-range, 0, '3000000'],
   ['starts-with', '$Content-Type', 'image/']
]
Run Code Online (Sandbox Code Playgroud)

This will limit uploads to 3Mb, and Content-Type to only items that begin with image/

Additionally, you only have to generate your signature for policy once (or whenever it changes), which means you don't need a request to your server to get a valid policy, you just hardcode it in your JS. When/if you need to update just regenerate the policy and signature and then update the JS file.

edit: There isn't a method through the SDK to do this as it's meant as way of directly POSTing from a form on a webpage, i.e. can work with no javascript.

edit 2: Full example of how to sign a policy using standard NodeJS packages:

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`);
Run Code Online (Sandbox Code Playgroud)