我在区域BUCKET_REGION上有一个名为BUCKET的S3存储桶.我试图允许我的网络和移动应用程序的用户将图像文件上传到这些存储桶,前提是它们满足基于内容类型和内容长度的某些限制(即,我希望仅允许小于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
在我的后端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.)
>我还看到了一个使用带有表单数据API的HTTP POST的更接近工作的例子.我想如果没有其他选择,这可能会完成它,但我担心它不再是“正确”的做事方式 – 此外,它似乎做了很多手动加密等而不使用官方节点SDK. (见https://stackoverflow.com/a/28638155/251162.)
解决方法
我认为在直接发布到S3的情况下,跳过后端服务器可能会更好.
您可以做的是定义一个策略,明确指定可以上传到哪些内容,然后使用AWS秘密访问密钥对该策略进行签名(使用AWS sig v4,可以使用this生成策略).
如果在AWS docs中可见,则使用策略和签名的示例
对于您的用途,您可以指定以下条件:
conditions: [ ['content-length-range,'3000000'],['starts-with','$Content-Type','image/'] ]
这会将上传限制为3Mb,内容类型仅限于以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,},'$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`);