1

我有一個Meteor Mobile應用程序,可以訪問存儲在我的S3存儲區中的大量照片。這些照片是用戶上傳並經常更改的。我不希望這些照片可供任何不使用我的應用的人訪問。 (即:這些照片只能從我的應用程序中查看,並且直接在瀏覽器中直接訪問網址不會加載它們)。從Meteor Mobile應用程序S3驗證

完成此操作的最佳方法是什麼? AWS Cognito似乎是合乎邏輯的選擇,但實現起來並不容易,我不確定如何在客戶端獲得Cognito身份後從客戶端向AWS進行身份驗證。

我的另一個想法是在每個網址上放一個只讀的AWS密鑰並進行身份驗證,但這幾乎沒有意義。找出關鍵和祕密真的很容易。

編輯:

具體而言,對於圖片的網址在蒙戈集合,我將它們傳遞到一個模板。所以,S3資源只需加載一個圖像標籤(<img src=")。像AWS STS這樣的東西聽起來像是一個很好的選擇,但我不知道在我像這樣加載它們時在標題中傳遞標記的方法。將它們作爲預先簽名的查詢字符串看起來效率不高。

另一種選擇是限制訪問referrer標題like this issue。但就像Martijn所說的那樣,這並不是一種安全的做法。

+0

在您的應用程序中,S3圖像訪問是否已通過身份驗證?如果是,您可以授予訪問該已驗證的GET的權限,並拒絕任何公開訪問您的存儲桶 – Mikkel

+0

不,現在他們只是在開發過程中公開可用的圖像,但我不想在生產中使用它。我不知道從客戶端進行身份驗證的方法,除了在URL或標頭中傳遞密鑰和祕密之外,兩者都可以看到。我想傳入標題會比URL更好,但不是一個可靠的解決方案。 – gkrizek

+0

不,你絕對不想傳遞密鑰,但是你可以使用Meteor服務器發送一個http認證請求(這在瀏覽器中是不可見的),以獲得你可以傳遞的認證令牌(我認爲)在圖片GET請求 – Mikkel

回答

0

經過一番研究和測試,我自己解決了這個問題。我的最終解決方案是使用referer標頭來限制對我的S3存儲桶的訪問。我創建了一個更安全,更詳細的解決方案(請參閱下文),但它帶來的性能下降對我的應用程序無效。我的應用基於查看照片和視頻,無法立即將它們加載到附近。雖然,我覺得這對大多數使用情況來說可能是一個充分的解決方案。因爲我的應用程序不是非常敏感,所以referer標題對我來說已經足夠了。 Here is how to use the http header referer to limit access to a bucket.

解決方案使用Amazon的STS:

首先,你需要有AWS SDK上serverclient.雙方有沒有最新的軟件包提供流星,所以我創造了我自己。 (我將很快發佈,並在這裏把一個鏈接,一旦我做的。)

在服務器上,您必須使用必須承擔一個角色的能力憑據。要承擔的角色必須與承擔角色的用戶建立信任關係。 Article on using IAM. - Article on using credentials with SDK

server.js文件中,我創建了一個Meteor方法,我可以從客戶端調用。它首先檢查用戶是否登錄。如果是這樣,它會檢查當前的臨時憑證是否將在接下來的5分鐘內過期。如果是,我發出新的憑據並將它們寫入用戶文檔或將它們作爲回調函數返回。如果他們在接下來的5分鐘內沒有過期,我將返回他們當前的臨時憑證。

您必須使用Meteor.bindEnvironment作爲回調。See docs

Meteor.methods({ 
'awsKey': function(){ 
    if (Meteor.userId()){ 
    var user = Meteor.userId(); 
    var now = moment(new Date()); 
    var userDoc = Meteor.users.findOne({_id: user}); 
    var expire = moment(userDoc.aws.expiration); 
    var fiveMinutes = 5 * 60 * 1000; 
    var fut = new Future(); 

    if(moment.duration(expire.diff(now))._milliseconds < fiveMinutes){ 
     var params = { 
      RoleArn: 'arn:aws:iam::556754141176:role/RoleToAssume', 
      RoleSessionName: 'SessionName', 
      DurationSeconds: 3600 //1 Hour 
     }; 
     var sts = new AWS.STS(); 
     sts.assumeRole(params, Meteor.bindEnvironment((err, data) => { 
      if (err){ 
       fut.throw(new Error(err)); 
      }else{ 
       Meteor.users.update({_id: user}, {$set: {aws: {accessKey: data.Credentials.AccessKeyId, secretKey: data.Credentials.SecretAccessKey, sessionToken: data.Credentials.SessionToken, expiration: data.Credentials.Expiration}}}); 
       fut.return(data.Credentials); 
      } 
      })); 
      return fut.wait(); 
     }else{ 
     return userDoc.aws; 
     } 
     } 
    } 
    } 
    }); 

然後可以手動或Meteor.startupsetInterval調用此方法。

Meteor.setInterval(function(){ 
    if(Meteor.userId()){ 
     Meteor.call('awsKey', function(err, data){ 
     if (err){ 
      console.log(err); 
     }else{ 
      if(data.accessKey){ 
      Session.set('accessKey', data.accessKey); 
      Session.set('secretKey', data.secretKey); 
      Session.set('sessionToken', data.sessionToken); 
      }else{ 
      Session.set('accessKey', data.AccessKeyId); 
      Session.set('secretKey', data.SecretAccessKey); 
      Session.set('sessionToken', data.SessionToken); 
      } 
     } 
     }); 
    } 
    }, 300000); //5 Minute interval 

這種方式只是從回調中設置Session變量中的鍵。你可以通過查詢用戶的文檔來獲得它們來做到這一點。

然後,您可以使用這些臨時憑據來獲取您試圖在存儲桶中訪問的對象的簽名URL。

我把這個模板助手在模板傳遞對象的名字吧:

{{getAwsUrl imageName}}

Template.templateName.helpers({ 

    'getAwsUrl': function(filename){ 
     var accessKey = Session.get('accessKey'); 
     var secretKey = Session.get('secretKey'); 
     var sessionToken = Session.get('sessionToken'); 
     var filename = filename; 
     var params = {Bucket: 'bucketName', Key: filename, Expires: 6000}; 
     new AWS.S3({accessKeyId: accessKey, secretAccessKey: secretKey, sessionToken: sessionToken, region: 'us-west-2'}).getSignedUrl('getObject', params, function (err, url) { 
     if (err) { 
     console.log("Error:" +err); 
     }else{ 
     result = url; 
     } 
    }); 
    return result; 
} 

}); 

這一切就是這麼簡單!我確信這可以被改進得更好,但這正是我在測試速度非常快時想到的。就像我說的那樣,它應該在大多數使用情況下工作。我特別的沒有。出於某種原因,當您嘗試在這些signedURL的img src上切換visibility: visible|hidden;時,它們將花費比加載URL更長的時間,而不僅僅是直接設置URL。這必須是因爲亞馬遜必須在返回對象之前解密簽名的URL。

感謝Mikkel的方向。