【问题标题】:Authenticating to S3 from Meteor Mobile Application从 Meteor 移动应用程序向 S3 进行身份验证
【发布时间】:2017-02-07 00:18:04
【问题描述】:

我有一个 Meteor Mobile 应用程序,它可以访问存储在我的 S3 存储桶中的大量照片。这些照片是用户上传的并且经常更改。我不希望没有使用我的应用程序的任何人都可以访问这些照片。 (即:这些照片只能从我的应用程序中查看,直接在浏览器中访问 url 不会加载它们)。

完成此任务的最佳方法是什么? AWS Cognito 似乎是合乎逻辑的选择,但它似乎并不容易实现,而且我不确定一旦获得 Cognito 身份后如何从客户端向 AWS 进行身份验证。

我的另一个想法是在每个 url 上放置一个只读的 AWS 密钥并以这种方式进行身份验证,但这几乎没有意义。找出密钥和秘密真的很容易。

编辑:

具体来说,图像的 URL 位于 Mongo 集合中,我将它们传递到模板中。因此,S3 资源只加载了一个图像标签 (<img src=")。像 AWS STS 这样的东西听起来是个不错的选择,但是当我像这样加载它们时,我不知道有一种方法可以在标头中传递标记。将它们作为预签名的查询字符串执行似乎效率低下。

另一个选项是使用referrer 标头like this issue 限制访问。但正如 Martijn 所说,这并不是一种真正安全的方式。

【问题讨论】:

  • 在您的应用中,S3 图像访问是否经过身份验证?如果是,您可以授予对该经过身份验证的 GET 的访问权限,并拒绝对您的存储桶的任何公开访问
  • 不,现在它们只是在开发中公开可用的图像,但我不希望在生产中使用它。除了在 url 或标头中传递密钥和秘密之外,我真的不知道从客户端进行身份验证的方法,这两者都可以看到。我想传入标头会比 URL 更好,但不是一个可靠的解决方案。
  • 不,您绝对不想传递密钥,但是您可以使用 Meteor 服务器发送 http 身份验证请求(从浏览器中看不到),以获取身份验证令牌(我认为)您可以在图像 GET 请求中传递它
  • 是的,这是个好主意,你的意思是像使用 AWS STS 来获取临时令牌吗?
  • 我不确定具体的细节,但就是这样,我会让你追到最后:)

标签: javascript amazon-web-services meteor amazon-s3 amazon-cognito


【解决方案1】:

经过一些研究和测试,我自己解决了这个问题。我的最终解决方案是使用 referer 标头来限制对我的 S3 存储桶的访问。我创建了一个更安全、更详细的解决方案(见下文),但它带来了对我的应用程序不起作用的性能损失。我的应用程序基于查看照片和视频,并且无法立即加载它们不在卡片中。虽然,我觉得它对于大多数用例来说可能是一个足够的解决方案。因为我的应用程序不是很敏感,所以 referer 标头对我来说就足够了。 Here is how to use the http header referer to limit access to a bucket.

使用亚马逊 STS 的解决方案:

首先,您需要在 serverclient. 上都有 AWS 开发工具包。没有适用于 Meteor 的最新软件包,所以我创建了自己的软件包。 (我会尽快发布并在此处放一个链接。)

在服务器上,您必须使用能够承担角色的凭据。要代入的角色必须与代入该角色的用户具有信任关系。 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.startup 上的setInterval 中调用此方法。

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

});

这就是它的全部内容!我确信这可以改进得更好,但这正是我在快速测试时想出的。就像我说的,它应该适用于大多数用例。我的特别没有。出于某种原因,当您尝试在这些签名 URL 中的 img src 上切换 visibility: visible|hidden; 时,它们的加载时间比直接设置 URL 要长得多。这一定是因为亚马逊必须在返回对象之前解密他们这边的签名 URL。

感谢Mikkel 的指导。

【讨论】:

    猜你喜欢
    • 2019-08-31
    • 2017-01-10
    • 2015-11-22
    • 1970-01-01
    • 2011-06-01
    • 2022-07-31
    • 2012-10-24
    • 2014-02-10
    相关资源
    最近更新 更多