2017-04-03 162 views
5

我在地區BUCKET_REGION上有一個名爲BUCKET的S3存儲桶。我試圖讓我的網絡和移動應用的用戶上傳圖片文件到這些存儲區,只要他們符合基於Content-TypeContent-Length(即,我只允許上傳小於3mb的jpegs)的某些限制。一旦上傳,這些文件應該公開訪問。允許用戶上傳內容到s3

基於相當廣泛通過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方法。


這裏有一些事情不工作:

  • 我已經看到了很多的提到與s3.getSignedUrl('putObject', ...)訪問(一個很舊的)API的。但是,這似乎並不支持可靠地設置ContentLength - 至少已經有了。 (請參閱https://stackoverflow.com/a/28699269/251162。)

  • 我也見過一個更貼近實際的例子,使用HTTP POSTform-data API,這也是非常古老的。我想如果沒有其他選擇,這可能會完成,但我擔心它不再是「正確」的做事方式 - 此外,它似乎做了大量的手動加密等,而不是使用官方節點SDK。 (見https://stackoverflow.com/a/28638155/251162。)

+0

可以確保您的S3存儲連接到它正確的策略:如何簽訂使用標準封裝的NodeJS政策完整的示例。它需要公開。 – LostJon

回答

3

我認爲在直接發佈到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:

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

嗨瑞安,謝謝指向非官方郵政政策NPM包。您是否知道使用官方的AWS節點SDK是否可以做到這一點?如果不是,你能否提供一個完整的賞金示範? (我可以拼湊在一起,自己如何做到這一點,但是如果能夠有一個權威的參考資料,就可以爲其他人做到這一點)。 –

+0

事情是,你不需要使用官方的SDK去做,因爲它是一個非常通用的過程(只有幾個hmac簽名組合)。你可以看看他們建議如何做簽名的文檔http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-javascript – Ryan

+0

我已經添加了一個類似於我已經用來生成策略的完整代碼示例。 – Ryan

2

這事我知道,我們有我們的項目,所以我會告訴你的部分代碼:

首先需要發佈到自己的服務器,以獲得信譽爲上傳, 從您將返回從客戶端上傳到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); 

當然,您從後端獲得的所有憑據項目。

+0

謝謝,但我不想發送一個通用的祕密AWS密鑰(即使是有限的IAM角色)給客戶端!你是否在某個地方生成了關鍵的特定祕密? –

+0

我知道這不是一個常量關鍵字,它是爲每個上傳請求生成的,所以不用擔心它是否會被劫持,因爲它會很快死亡 –

+0

您是如何生成密鑰的?以及如何確保用戶不上傳大於3MB的文件? –

2

在後端,您需要生成一個定時的預先登記的URL並將該URL發送到客戶端以訪問S3對象。根據您的後端實施技術,您可以使用AWS CLI或SDK(例如Java,.Net,Ruby或Go)。

請參考CLI docsSDK docsmore SDK

內容大小限制不鏈接產生直接支持。但鏈接只是爲了中繼AWS用戶擁有的訪問權限。

要使用策略限制上傳文件大小,您必須在存儲段上創建CORS策略並使用HTTP POST進行上載。請參閱this link

+0

正如我在問題中提到的那樣,至少在Node API中,並且據我通過其他瀏覽器可以看出,s3.getSignedUrl()函數不允許設置內容大小的限制。如果你能提供一個簽名內容長度受限制的URL的例子,在這裏會很高興錯誤。 –

+0

直接鏈接生成不支持內容大小限制。但鏈接只是爲了中繼AWS用戶擁有的訪問權限。 –

+0

您能否描述在後端使用Node SDK以及在前端使用現代AJAX庫來實現POST策略等的「正確」方式? –

2

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

使用亞馬遜Cognito同步,你可以檢索跨客戶平臺,設備和操作系統的數據,因此,如果用戶開始使用手機上的應用程序,並稍後切換到平板電腦,持久化的應用程序信息仍可供該用戶使用。

在這裏閱讀更多:Cognito identity pools

一旦你創建新的標識池,你可以參考它在使用JavaScript的S3 SDK,這將允許你上傳的內容絲毫暴露出來任何憑據到客戶端。

這裏舉例:Uploading to S3

請通讀這一切,尤其是部分「配置SDK」。

拼圖的第二部分 - 驗證。

我會去執行客戶端驗證(如果可能的話),以避免在發生錯誤之前的網絡延遲。如果您選擇在S3或AWS Lambda上實施驗證,則需要等待文件達到AWS的等待時間 - 網絡延遲。

+0

驗證當然需要在客戶端進行,但也需要在服務器端進行安全性驗證。 –

+0

亞倫,當您上傳到S3時,您唯一的選擇是AWS Lambda,否則您需要實現另一個後端層。您可以爲您的存儲桶指定AWS Lambda,並在出現錯誤時讓回調進行大小和內容類型檢查 - 刪除對象並在前端進行反思。使用NodeJS for Lambda,它具有支持ImageMagick的構建。識別ImageMagick的方法將提供所有需要的信息。 – rock3t

+0

是否有理由選擇Cognito + Lambda進行驗證,而不是'a3.getSignedUrl()'和發佈策略? –

3

您的服務器就像一個代理,也負責授權,驗證等 一些代碼片段:

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

,然後你可以把密鑰發送給客戶,並提供進一步的訪問。

+0

這絕對是這樣做的一種方式 - 所有間接方式(在我的問題和其他答案中)的原因是我們希望避免使用S3流量加載應用程序服務器。 –

+1

會建議創建另一個負責處理推/拉/更新文件的服務。 – someUser