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,在我的情况下,它可以通过几个步骤工作:
它有主要的代码部分:https://gist.github.com/zxbodya/3cdabd9172bcc89f8ac5
请添加
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); }); |