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

时间:2013-10-11 16:14:28

标签: angularjs amazon-s3 cors

所以我在将文件直接上传到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});
});

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

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进行放置。

2 个答案:

答案 0 :(得分:8)

我在这个问题上一直在苦苦挣扎,终于搞清楚了!我将详细说明我的步骤,希望它可以帮助一些人。

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

我按照他们列出的步骤,即:

  1. 创建一个Bucket
  2. 授予“放置/删除:展开”权限“部分,然后点击”添加更多权限“按钮。选择”所有人“和”上传/删除“并保存。
  3. 添加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>
    

  4. 将“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>
    
  5. 使用以下命令创建将返回JSON的服务:

    {
       "policy":"XXX",
       "signature":"YYY",
       "key":"ZZZ"
    }
    
  6.   

    • XXX - AWS所需的策略json,base64编码。
    • YYY - HMAC和私人密钥
    • 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

    不允许发送'localhost:3000'

    从您的错误看,您似乎尚未在AWS端设置CORS规则。

答案 1 :(得分:4)

这个例子可能会有所帮助: https://github.com/bookingbricks/file-upload-example 使用:Node,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');
    });