关于javascript:如何使用预签名URL代替凭证直接从浏览器上传到AWS S3?

How to upload to AWS S3 directly from browser using a pre-signed URL instead of credentials?

我们想使用Javascript AWS开发工具包将文件上传到S3,但完全不使用凭证。
使用凭证进行上传是可行的,但是我们无法为我们的每个应用程序用户生成一个AWS IAM用户(或者我们应该吗?)

因此,类似于使用GET,我们希望服务器生成一个预签名的URL,将其发送到浏览器,然后将浏览器上传到该URL。

但是,没有有关如何完成此操作的示例。
另外,如果未设置凭据,则即使在进行上传到S3请求之前,SDK也会出现错误

1
2
code:"CredentialsError"
message:"No credentials to load"

JS SDK文档提到了这一点,因此似乎有可能:

1
2
3
4
5
Pre-signing a putObject (asynchronously)
var params = {Bucket: 'bucket', Key: 'key'};
    s3.getSignedUrl('putObject', params, function (err, url) {
      console.log('The URL is', url);
});


静默旧的问题,但这确实对我有所帮助,最终使它完成。
我的解决方案基于带有jQuery的PHP和JavaScript。

我在https://github.com/JoernBerkefeld/s3SignedUpload中很好地包装了整个解决方案,但是这里是要点:

api.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
require_once '/server/path/to/aws-autoloader.php';
use Aws\\Common\\Aws;

$BUCKET ="my-bucket";
$CONFIG ="path-to-iam-credentials-file-relative-to-root.php"

function getSignedUrl($filename, $mime) {
    $S3 = Aws::factory( $CONFIG )->get('S3');
    if(!$filename) {
        return $this->error('filename missing');
    }
    if(!$mime) {
        return $this->error('mime-type missing');
    }
    $final_filename = $this->get_file_name($filename);
    try {
        $signedUrl = $S3->getCommand('PutObject', array(
            'Bucket' => $BUCKET,
            'Key' => $this->folder . $final_filename,
            'ContentType' => $mime,
            'Body'        => '',
            'ContentMD5'  => false
        ))->createPresignedUrl('+30 minutes');
    } catch (S3Exception $e) {
        echo $e->getMessage() ."\
"
;
    }
    $signedUrl .= '&Content-Type='.urlencode($mime);
    return $signedUrl;
}


echo getSignedUrl($_GET['filename'],$_GET['mimetype']);

请确保将用户身份验证添加到您的api.php中。其他每个知道该文件路径的人都可以将文件上传到您的存储桶。

certificate.inc.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
return array(
    'includes' => array('_aws'),
    'services' => array(
        'default_settings' => array(
            'params' => array(
                'key'    => 'MY-ACCESS-KEY',
                'secret' => 'MY-SECRECT',
                'region'  => 'eu-west-1' // set to your region
            )
        )
    )
);

client.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$("input[type=file]").onchange = function () {
    for (var file, i = 0; i < this.files.length; i++) {
        file = this.files[i];
        $.ajax({
            url : s3presignedApiUri,
            data: 'file='+ file.name + '&mime=' + file.type,
            type :"GET",
            dataType :"json",
            cache : false,
        })
        .done(function(s3presignedUrl) {
            $.ajax({
                url : s3presignedUrl,
                type :"PUT",
                data : file,
                dataType :"text",
                cache : false,
                contentType : file.type,
                processData : false
            })
            .done(function(){
                console.info('YEAH', s3presignedUrl.split('?')[0].substr(6));
            }
            .fail(function(){
                console.error('damn...');
            }
        })
    }
};

s3 cors设置(实际上需要" PUT&OPTIONS",但不能直接启用OPTIONS ...):

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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>
        <AllowedMethod>HEAD</AllowedMethod>
        <AllowedMethod>DELETE</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>


如果您不使用jQuery,则这是前端所需的最低要求:

1
2
3
4
5
6
7
8
9
10
11
12
var xhr = new XMLHttpRequest();
xhr.open('PUT', signedUrl, true);
xhr.setRequestHeader('Content-Type', signedUrlContentType);
xhr.onload = () => {
  if (xhr.status === 200) {
    // success!
  }
};
xhr.onerror = () => {
  // error...
};
xhr.send(file); // `file` is a File object here

请参阅文件对象文档:https://developer.mozilla.org/en-US/docs/Web/API/File

然后,您可以照常添加上传进度:

1
2
3
4
5
6
xhr.upload.onprogress = (event) => {
  if (event.lengthComputable) {
    var percent = Math.round((event.loaded / event.total) * 100)
    console.log(percent);
  }
};


在项目中,就我现在正在执行的工作而言,我将文件直接从客户端上传到S3,在我的情况下,它可以通过几个步骤工作:

  • 从服务器请求带有上载设置的预签名表单(它是在服务器上签名的,因为我无法将访问密钥传递给客户端,并且还需要应用一些限制才能上载)
  • 使用XHR2将文件上传到S3(对于旧的浏览器,您可以对隐藏的iframe或Flash等浏览器插件使用hack)
  • 它有主要的代码部分:https://gist.github.com/zxbodya/3cdabd9172bcc89f8ac5


    请添加ACLContentType,它将使其正常运行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const param = {
          Bucket: 'Bucket',
          Key: 'fiileName',
          ACL: 'public-read',
          ContentType: 'fileType'
        };
    s3.getSignedUrl('putObject', param, function (err, url) {
             console.log('The URL is', url);
        });

    我更喜欢通过github这种更清洁的方法:

    如果您已经为浏览器生成了预签名URL,则只需发送带有该URL和有效负载的XHR请求即可上传到S3。 SDK不需要这样做。下面的jQuery示例:

    1
    2
    3
    4
    5
    6
    $.ajax({
      url: presignedUrl, // the presigned URL
      type: 'PUT',
      data: 'data to upload into URL',
      success: function() { console.log('Uploaded data successfully.'); }
    });


    我可以建议两种方法:

    1-您可以在您的应用中生成带有一个凭证的预签名表格

    参见文档:http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTForms.html

    2-您可以使用网络身份联盟,并使用google,facebook或amazon登录:

    参见文档:http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-configuring-wif.html

    游乐场:http://aws.typepad.com/aws/2013/08/the-aws-web-identity-federation-playground.html


    产生网址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const AWS = require("aws-sdk");
    const s3 = new AWS.S3({
       endpoint: 's3-ap-south-1.amazonaws.com',   // Put you region
       accessKeyId: 'AKXXXXXXXXXXXXXXXA6U',       // Put you accessKeyId
       secretAccessKey: 'kzFHoXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXssoGp',   // Put you accessKeyId
       Bucket: 'Bucket-Name',         // Put your bucket name
       signatureVersion: 'v4',
       region: 'ap-south-1'           // Put you region
    });

    const getSingedUrlforPut = async () => {
    const params = {
        Bucket: 'Bucket-Name',
        Key: '317ec11af14a46b89f400bcf8f9fff1222.pdf',
        Expires: 60 * 5
      };
    try {
        const url = await new Promise((resolve, reject) => {
          s3.getSignedUrl('putObject', params, (err, url) => {
            err ? reject(err) : resolve(url);
          });
        });
        console.log(url)
      } catch (err) {
        if (err) {
          console.log(err)
        }
      }
    }
    getSingedUrlforPut()

    通过ajax上传文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var form = new FormData();
    form.append("", fileInput.files[0],"director_pan_af8ef2d261c46877f95038622c96e7c0.pdf");
    var settings = {
     "url":"https://sme-testing.s3-ap-south-1.amazonaws.com/317ec11af14a46b89f400bcf8f9fff1222.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIXXXXXXXXXXXX6U%2F20200525%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=20200525T083419Z&X-Amz-Expires=300&X-Amz-Signature=ea063731d7d043b62d0dc7c0984f4d5792c7f7f41e9ffb52a97d62adadcef422&X-Amz-SignedHeaders=host",
     "method":"PUT",
     "timeout": 0,
     "processData": false,
     "mimeType":"multipart/form-data",
     "contentType": false,
     "data": form
    };
    $.ajax(settings).done(function (response) {
      console.log(response);
    });