使用签名的 url 将文件从 angularjs 直接上传到 amazon s3

Posted

技术标签:

【中文标题】使用签名的 url 将文件从 angularjs 直接上传到 amazon s3【英文标题】:upload file from angularjs directly to amazon s3 using signed url 【发布时间】:2013-10-19 19:16:03 【问题描述】:

所以我在将文件直接上传到 S3 时遇到了一些问题。目前我的流程是向 nodejs/express 发出请求以获取签名 URL。

app.post('/s3SignedURL', function(req, res)
  var id = crypto.randomBytes(20).toString('hex');
  var ext = path.extname(req.body.fileName);
  var unambFilename = path.basename(req.body.fileName, ext) + '-' + id + ext;
  var params = Bucket: awsBucket, Key: unambFilename, Expires: 30;
  var signedUrl = s3.getSignedUrl('putObject', params);

  res.send(signedUrl: signedUrl, s3FileName: unambFilename);
);

然后我的 Angular 控制器尝试使用该签名 URL ($scope.uploadDocument()) 直接上传到 s3

flqApp.controller('DocUploadModalCtrl', ['$scope', '$http', 'customProvider', 'custom',
  function($scope, $http, customProvider, custom)

  $scope.fileTypes = 
  [
    "Type 1",
    "Type 2"
  ]

  $scope.setFile = function(element)
    $scope.$apply(function($scope)
      $scope.currentDocument = element.files[0];
    );
  

  $scope.uploadDocument = function() 
    $http.post('/s3SignedURL', fileName: $scope.currentDocument.name )
     .success(function(results)
      $http.put(results.signedUrl, $scope.currentDocument)
       .success(function()
        custom.document = s3FileName;
        customProvider.save(custom, function()
        //..do something here
        );
      );
    );
  ;
]);

我的 html 表单看起来像

<form ng-submit="uploadDocument()">
  <label for="documentType">File Type</label>
  <select class="form-control" ng-model="docType" ng-options="type for type in fileTypes" required >
    <option value=""/>
  </select>
  <label for="filename">Choose file to upload</label>
  <input type="file"
     name="s3File"
     onchange="angular.element(this).scope().setFile(this)"
     ng-model="fileName"
     required />

  <input type="submit" value="Upload File">
</form>

但是,每当我尝试上传到 S3 时,我都会收到错误

Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin

我知道在亚马逊端为该存储桶正确设置了 S3 CORS,因为我开发了使用相同存储桶进行开发存储的 ruby​​ 应用程序。 (当然我用回形针和雾来做这些)。其次,由于我没有亚马逊响应的失败捕获,我不怀疑错误来自那里。但是,它确实来自我尝试将文件放在亚马逊上的那一行。

所以我确定我遗漏了一些东西,但我认为使用签名 URL 时,我只需要对那个 URL 进行放置即可。

【问题讨论】:

只是好奇......你有没有在 Angular 中得到这个工作?我正准备做类似的事情。 从来没有让它工作。最终上传到我的 node.js 服务器并使用亚马逊的 node 包上传。 @nbppp2 您是否尝试过使用 ng-s3upload 和 rails 应用程序进行文件上传? 所以我正在开发的应用程序是在 Node 中而不是在 Rails 中,但是在 nodejs 中下面的 rails 示例代码有等价物。可悲的是,在给出答案时,我们已经以不同的方式实现了它。由于它为我们提供了其他好处,我们决定保持这种不同的方式。我确实回去尝试了这个模块,并且能够让它在 Angular 和 nodejs 中工作。但是我回去看看我是否能找到我的代码,但似乎我删除了它。可能在我认为我不会再去的本地分支上。 【参考方案1】:

我一直在努力解决这个问题,终于弄明白了!我将详细说明我的步骤,希望它可以帮助一些人。

我用过这个模块:https://github.com/asafdav/ng-s3upload

我按照他们列出的步骤进行操作,即:

    创建存储桶 授予“放置/删除”权限:展开“权限”部分并点击“添加更多权限”按钮。选择“所有人”和“上传/删除”并保存。

    添加CORS配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
        <CORSRule>
            <AllowedOrigin>*</AllowedOrigin>
            <AllowedMethod>GET</AllowedMethod>
            <AllowedMethod>POST</AllowedMethod>
            <AllowedMethod>PUT</AllowedMethod>
            <AllowedHeader>*</AllowedHeader>
        </CORSRule>
    

    将“crossdomain.xml”添加到您的存储桶的根目录,使其公开

    <?xml version="1.0"?>
    <!DOCTYPE cross-domain-policy SYSTEM
    "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
    <cross-domain-policy>
      <allow-access-from domain="*" secure="false" />
    </cross-domain-policy>
    

    创建一个返回 JSON 的服务:

    
       "policy":"XXX",
       "signature":"YYY",
       "key":"ZZZ"
    
    

XXX - AWS 所需的策略 json,base64 编码。 YYY - 您的私钥的 HMAC 和 sha ZZZ - 您的公钥 这是一个 rails 示例,即使您不是 rails 开发人员,请阅读代码, 这非常简单。

这是最重要的一步:确保生成正确的策略文档。

这是我的 C# 代码

            StringBuilder builder = new StringBuilder();
        builder.Append("")
                .Append("\"expiration\": \"")
                .Append(GetFormattedTimestamp(expireInMinutes))
                .Append("\",")
                .Append("\"conditions\": [")
                .Append("\"bucket\": \"")
                .Append(bucketName)
                .Append("\",")
                .Append("\"acl\": \"")
                .Append("public-read")
                .Append("\",")
                .Append("[\"starts-with\", \"$key\", \"")
                .Append(prefix)
                .Append("\"],")
                .Append("[\"starts-with\", \"$Content-Type\", \"\"],")                    
                .Append("[ \"content-length-range\", 0, " + 10 * 1024 * 1024 + "]")
                .Append("]");
        Encoding encoding = new UTF8Encoding();
        this.policyString = Convert.ToBase64String(encoding.GetBytes(builder.ToString().ToCharArray()));
        this.policySignature = SignPolicy(awsSecretKey, policyString);

这会生成以下 Json


   "expiration":"2014-02-13T15:17:40.998Z",
   "conditions":[
      
         "bucket":"bucketusaa"
      ,
      
         "acl":"public-read"
      ,
      [
         "starts-with",
         "$key",
         ""
      ],
      [
         "starts-with",
         "$Content-Type",
         ""
      ],
      [
         "content-length-range",
         0,
         10485760
      ]
   ]

然后这个文档被base64编码并作为一个字符串发送出去。

我的问题与我的政策文件有关。策略文档就像您为会话定义的一组规则,例如:文件名必须以某些内容开头(即上传到子文件夹),大小必须在范围内。

为您的浏览器使用开发人员工具,并查看网络选项卡,看看 AWS 返回了哪些错误,这对我很有帮助,它会说明诸如策略错误之类的内容并说明什么条件失败。您通常会收到拒绝访问错误,这取决于政策文档中设置的条件或错误的键。

另一件事,一些浏览器在 localhost CORS 方面存在问题。但是使用上面的方法,我可以使用 chrome 从我的本地开发机器上传文件。

Access-Control-Allow-Origin 不允许Origin 'localhost:3000'

从您的错误来看,您似乎没有在 AWS 端设置 CORS 规则。

【讨论】:

嗨,我也在尝试使用 ng-S3upload,你能告诉我应该在 s3-upload-options="getOptionsUri: 's3OptionsUri' 中输入什么吗? 这是您将 url 放入您创建的服务的位置,这将为 ng-s3Upload 提供 "policy":"XXXX", "signature":"YYY", "key": "ZZZ" 对象如上所述,例如:您将有 options="getOptionsUri: '/api/getSig'" 假设您的服务在 /api/getSig 完整示例 非常感谢!目前,我面临签名不匹配的错误,你能帮我吗?我遵循 ng-s3upload,但收到此错误。你以前有过这个问题吗? 我的签名是由策略和秘密访问密钥生成的,公钥是亚马逊提供的访问密钥id,对吗? 是的,访问密钥是亚马逊提供的您的公钥。您永远不会希望以明文形式传输您的密钥,这将是一个重大的安全漏洞,这就是您使用您的私钥“签署”要通过网络传输的数据的原因。在 aws 方面,亚马逊还可以在给定公钥的情况下使用您的密钥签署您发送的策略,并查看结果是否匹配。【参考方案2】:

这个例子可能会有所帮助: https://github.com/bookingbricks/file-upload-example 使用:节点、aws-sdk-js、jQuery-file-upload (blueimp)

服务器:

var AWS = require('aws-sdk');

AWS.config.update(accessKeyId: AWS_ACCESS_KEY, secretAccessKey:     AWS_SECRET_KEY);
AWS.config.region = 'eu-west-1';

app.post('/s', function (req, res) 
    var s3 = new AWS.S3();
    var params = Bucket: 'BUCKETNAME', Key: req.body.name, ContentType: req.body.type;
    s3.getSignedUrl('putObject', params, function(err, url) 
        if(err) console.log(err);
        res.json(url: url);
    );
);

客户:

$.ajax(
    url: '/s',
    type: 'POST',
    data: name: file.name, size: file.size, type:file.type,
).success(function(res)
    $.ajax(
        url: res.url,
        type: 'PUT',
        data: file,
        processData: false,
        contentType: file.type,
    ).success(function(res)
        console.log('Done');
    );

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。

以上是关于使用签名的 url 将文件从 angularjs 直接上传到 amazon s3的主要内容,如果未能解决你的问题,请参考以下文章

使用其预签名 URL 从 AWS s3 读取文件的内容

使用 ajax 通过签名的 url 将文件上传到谷歌云存储

使用 Filesystem Laravel 5.2 从 amazon s3 获取文件的签名 URL

如何使用 angularjs 从 url 下载(保存)图像到我们的相册中?

使用 AngularJS 从 JSON 响应中获取图像 url

如何使用签名的 URL 将文件上传到谷歌云存储桶