首先我要说的是,我通常非常不愿意发布这些问题,因为我一直觉得对互联网上的所有内容都有答案。在花了无数个小时寻找这个问题的答案之后,我终于放弃了这个陈述。
这有效:
s3.getSignedUrl('putObject', params);
注意:如果有一种更简单的方法允许客户端(iPhone)使用预先签名的URL上传到Amazon S3(并且没有公开客户端的凭据),我会全力以赴。
AccessDenied
错误。在NodeJS中,我生成一个预先签名的URL,如下所示:
var params = {Bucket: mybucket, Key: "test.jpg", Expires: 600};
s3.getSignedUrl('putObject', params, function (err, url){
console.log(url); // this is the pre-signed URL
});
预签名网址如下所示:
https://mybucket.s3.amazonaws.com/test.jpg?AWSAccessKeyId=AABFBIAWAEAUKAYGAFAA&Expires=1391069292&Signature=u%2BrqUtt3t6BfKHAlbXcZcTJIOWQ%3D
现在我通过PUT上传文件
curl -v -T myimage.jpg https://mybucket.s3.amazonaws.com/test.jpg?AWSAccessKeyId=AABFBIAWAEAUKAYGAFAA&Expires=1391069292&Signature=u%2BrqUtt3t6BfKHAlbXcZcTJIOWQ%3D
问题
我得到上面列出的 *主要问题
我也尝试在代码中添加Content-Type和x-amz-acl,方法是替换params:
var params = {Bucket: mybucket, Key: "test.jpg", Expires: 600, ACL: "public-read-write", ContentType: "image/jpeg"};
然后我尝试了一个很好的'PUT:
curl -v -H "image/jpeg" -T myimage.jpg https://mybucket.s3.amazonaws.com/test.jpg?AWSAccessKeyId=AABFBIAWAEAUKAYGAFAA&Content-Type=image%2Fjpeg&Expires=1391068501&Signature=0yF%2BmzDhyU3g2hr%2BfIcVSnE22rY%3D&x-amz-acl=public-read-write
问题
我的终端输出了一些错误:
-bash: Content-Type=image%2Fjpeg: command not found
-bash: x-amz-acl=public-read-write: command not found
我也得到了上面列出的 *主要问题。
下面列出的所有项目都在AWS管理控制台中打勾
Grantee: Everyone can [List, Upload/Delete, View Permissions, Edit Permissions]
Grantee: Authenticated Users can [List, Upload/Delete, View Permissions, Edit Permissions]
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1390381397000",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::mybucket/*"
}
]
}
我将用户政策设置为:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
AuthenticatedUsers组策略是这样的:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1391063032000",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"*"
]
}
]
}
我将CORS政策设置为:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
而且......现在我在这里。
答案 0 :(得分:10)
<强> 更新 强>
我有坏新闻。根据SDK {2.1}在http://aws.amazon.com/releasenotes/1473534964062833发布的说明:
"The SDK will now throw an error if ContentLength is passed into an
Amazon S3 presigned URL (AWS.S3.getSignedUrl()). Passing a
ContentLength is not supported by the SDK, since it is not enforced on
S3's side given the way the SDK is currently generating these URLs.
See GitHub issue #457."
我发现在某些场合,必须包含ContentLength(特别是如果你的客户端通过它以便签名匹配),那么在其他场合,如果你包含ContentLength参数错误,getSignedUrl会抱怨:&#34; contentlength预约网址不支持&#34;。我注意到当我更改正在进行呼叫的机器时,行为会发生变化。据推测,另一台机器与该场中的另一台亚马逊服务器建立了连接。
我只能猜测为什么行为在某些情况下存在,而在其他情况下则不存在。也许并非所有亚马逊的服务器都已完全升级?在任何一种情况下,为了处理这个问题,我现在尝试使用ContentLength,如果它给我参数错误,那么我在没有它的情况下再次调用getSignedUrl。这是解决SDK的这种奇怪行为的解决方法。
一个小例子......看起来不是很漂亮,但你明白了:
MediaBucketManager.getPutSignedUrl = function ( params, next ) {
var _self = this;
_self._s3.getSignedUrl('putObject', params, function ( error, data ) {
if (error) {
console.log("An error occurred retrieving a signed url for putObject", error);
// TODO: build contextual error
if (error.code == "UnexpectedParameter" && error.message.search("ContentLength") > -1) {
if (params.ContentLength) delete params.ContentLength
MediaBucketManager.getPutSignedUrl(bucket, key, expires, params, function ( error, data ) {
if (error) {
console.log("An error occurred retrieving a signed url for putObject", error);
} else {
console.log("Retrieved a signed url for putObject:", data);
return next(null, data)
}
});
} else {
return next(error);
}
} else {
console.log("Retrieved a signed url for putObject:", data);
return next(null, data);
}
});
};
所以,下面的内容并不完全正确(在某些情况下会更正,但在其他情况下会给你参数错误),但可能会帮助你开始。
旧答案
似乎(对于一个signedUrl将文件PUT输出到只存在公共读取ACL的S3),当向PUT发出请求时,会有几个标头被比较。将它们与传递给getSignedUrl的内容进行比较:
CacheControl: 'STRING_VALUE',
ContentDisposition: 'STRING_VALUE',
ContentEncoding: 'STRING_VALUE',
ContentLanguage: 'STRING_VALUE',
ContentLength: 0,
ContentMD5: 'STRING_VALUE',
ContentType: 'STRING_VALUE',
Expires: new Date || 'Wed De...'
请在此处查看完整列表:http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
当你打电话给getSignedUrl时,你会传递一个&#39; params&#39;对象(在文档中相当清楚),包括Bucket,Key和Expires数据。这是一个(NodeJS)示例:
var params = { Bucket:bucket, Key:key, Expires:expires };
s3.getSignedUrl('putObject', params, function ( error, data ) {
if (error) {
// handle error
} else {
// handle data
}
});
不太清楚的是将ACL设置为&#39; public-read&#39;:
var params = { Bucket:bucket, Key:key, Expires:expires, ACL:'public-read' };
传递标题的概念非常模糊,您期望客户端使用签名的URL将PUT操作传递给S3:
var params = {
Bucket:bucket,
Key:key,
Expires:expires,
ACL:'public-read',
ContentType:'image/png',
ContentLength:7469
};
在上面的示例中,我已经包含了ContentType和ContentLength,因为在javascript中使用XmlHTTPRequest时会包含这两个标头,并且在Content-Length的情况下无法更改。我怀疑像Curl这样的HTTP请求的其他实现会出现这种情况,因为在提交包含正文(数据)的HTTP请求时它们是必需的标头。
如果客户端在请求signedUrl时没有包含有关该文件的ContentType和ContentLength数据,那么当需要将文件PUT到S3(使用该signedUrl)时,S3服务将找到客户端附带的头文件&# 39; s请求(因为它们是必需的标题)但签名不会包含它们 - 因此,它们将不匹配,操作将失败。
因此,在进行getSignedUrl调用之前,您似乎必须知道文件的内容类型和内容长度为PUT到S3。这对我来说不是问题,因为我暴露了一个REST端点,允许我们的客户端在对PUT进行S3操作之前请求签名的URL。由于客户端可以访问要提交的文件(在他们准备提交的时刻),客户端访问文件大小和类型并从我的端点请求带有该数据的签名URL是一项微不足道的操作。 / p>
答案 1 :(得分:5)
根据@Reinsbrain请求,这是实现客户端上传到具有“公共读取”权限的服务器的Node.js版本。
BACKEND(NODE.JS)
var AWS = require('aws-sdk');
var AWS_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY;
var AWS_SECRET_ACCESS_KEY = process.env.S3_SECRET;
AWS.config.update({accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY});
var s3 = new AWS.S3();
var moment = require('moment');
var S3_BUCKET = process.env.S3_BUCKET;
var crypto = require('crypto');
var POLICY_EXPIRATION_TIME = 10;// change to 10 minute expiry time
var S3_DOMAIN = process.env.S3_DOMAIN;
exports.writePolicy = function (filePath, contentType, maxSize, redirect, callback) {
var readType = "public-read";
var expiration = moment().add('m', POLICY_EXPIRATION_TIME);//OPTIONAL: only if you don't want a 15 minute expiry
var s3Policy = {
"expiration": expiration,
"conditions": [
["starts-with", "$key", filePath],
{"bucket": S3_BUCKET},
{"acl": readType},
["content-length-range", 2048, maxSize], //min 2kB to maxSize
{"redirect": redirect},
["starts-with", "$Content-Type", contentType]
]
};
// stringify and encode the policy
var stringPolicy = JSON.stringify(s3Policy);
var base64Policy = Buffer(stringPolicy, "utf-8").toString("base64");
// sign the base64 encoded policy
var testbuffer = new Buffer(base64Policy, "utf-8");
var signature = crypto.createHmac("sha1", AWS_SECRET_ACCESS_KEY)
.update(testbuffer).digest("base64");
// build the results object to send to calling function
var credentials = {
url: S3_DOMAIN,
key: filePath,
AWSAccessKeyId: AWS_ACCESS_KEY_ID,
acl: readType,
policy: base64Policy,
signature: signature,
redirect: redirect,
content_type: contentType,
expiration: expiration
};
callback(null, credentials);
}
FRONTEND 假设服务器的值在输入字段中,并且您通过表单提交提交图像(即POST,因为我无法让PUT工作):
function dataURItoBlob(dataURI, contentType) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: contentType});
}
function submitS3(callback) {
var base64Data = $("#file").val();//your file to upload e.g. img.toDataURL("image/jpeg")
var contentType = $("#contentType").val();
var xmlhttp = new XMLHttpRequest();
var blobData = dataURItoBlob(base64Data, contentType);
var fd = new FormData();
fd.append('key', $("#key").val());
fd.append('acl', $("#acl").val());
fd.append('Content-Type', contentType);
fd.append('AWSAccessKeyId', $("#accessKeyId").val());
fd.append('policy', $("#policy").val());
fd.append('signature', $("#signature").val());
fd.append("redirect", $("#redirect").val());
fd.append("file", blobData);
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4) {
//do whatever you want on completion
callback();
}
}
var someBucket = "your_bucket_name"
var S3_DOMAIN = "https://"+someBucket+".s3.amazonaws.com/";
xmlhttp.open('POST', S3_DOMAIN, true);
xmlhttp.send(fd);
}
注意:我每次提交时都上传了超过1张图片,因此我添加了多个iframe(上面带有FRONTEND代码)来同时上传多张图片。
答案 2 :(得分:3)
第1步:设置s3政策:
{
"expiration": "2040-01-01T00:00:00Z",
"conditions": [
{"bucket": "S3_BUCKET_NAME"},
["starts-with","$key",""],
{"acl": "public-read"},
["starts-with","$Content-Type",""],
["content-length-range",0,524288000]
]
}
第2步:准备aws密钥,策略,签名,在本例中,所有存储在s3_tokens字典中
这里的诀窍在于政策&amp;签名 政策: 1)在文件中保存第1步策略。将其转储到json文件。 2)base 64编码的json文件(s3_policy_json):
#python
policy = base64.b64encode(s3_policy_json)
签名:
#python
s3_tokens_dict['signature'] = base64.b64encode(hmac.new(AWS_SECRET_ACCESS_KEY, policy, hashlib.sha1).digest())
第3步:来自你的js
$scope.upload_file = function(file_to_upload,is_video) {
var file = file_to_upload;
var key = $scope.get_file_key(file.name,is_video);
var filepath = null;
if ($scope.s3_tokens['use_s3'] == 1){
var fd = new FormData();
fd.append('key', key);
fd.append('acl', 'public-read');
fd.append('Content-Type', file.type);
fd.append('AWSAccessKeyId', $scope.s3_tokens['aws_key_id']);
fd.append('policy', $scope.s3_tokens['policy']);
fd.append('signature',$scope.s3_tokens['signature']);
fd.append("file",file);
var xhr = new XMLHttpRequest();
var target_url = 'http://s3.amazonaws.com/<bucket>/';
target_url = target_url.replace('<bucket>',$scope.s3_tokens['bucket_name']);
xhr.open('POST', target_url, false); //MUST BE LAST LINE BEFORE YOU SEND
var res = xhr.send(fd);
filepath = target_url.concat(key);
}
return filepath;
};
答案 3 :(得分:1)
您实际上可以使用上面指定的getSignedURL。以下是如何获取从S3读取的URL,以及使用getSignedURL发布到S3的示例。上传的文件具有与用于生成URL的IAM用户相同的权限。您注意到的问题可能与您如何使用curl进行测试有关?我使用AFNetworking(AFHTTPSessionManager uploadTaskWithRequest)从我的iOS应用程序上传。以下是有关如何使用签名网址发布的示例:http://pulkitgoyal.in/uploading-objects-amazon-s3-pre-signed-urls/
var s3 = new AWS.S3(); // Assumes you have your credentials and region loaded correctly.
这是从S3读取的。 URL将工作60秒。
var params = {Bucket: 'mys3bucket', Key: 'file for temp access.jpg', Expires: 60};
var url = s3.getSignedUrl('getObject', params, function (err, url) {
if (url) console.log("The URL is", url);
});
这是写入S3的。 URL将工作60秒。
var key = "file to give temp permission to write.jpg";
var params = {
Bucket: 'yours3bucket',
Key: key,
ContentType: mime.lookup(key), // This uses the Node mime library
Body: '',
ACL: 'private',
Expires: 60
};
var surl = s3.getSignedUrl('putObject', params, function(err, surl) {
if (!err) {
console.log("signed url: " + surl);
} else {
console.log("Error signing url " + err);
}
});
答案 4 :(得分:0)
听起来您并不真正需要签名的网址,只是希望您的上传内容可以公开查看。如果是这种情况,您只需要转到AWS控制台,选择要配置的存储桶,然后单击权限。然后单击“添加存储桶策略”按钮并输入以下规则:
{
"Version": "2008-10-17",
"Id": "http referer policy example",
"Statement": [
{
"Sid": "readonly policy",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKETNAME/*"
}
]
}
其中BUCKETNAME应替换为您自己的存储桶名称。现在任何人都可以读取该存储桶的内容,前提是它们具有指向特定文件的直接链接。
答案 5 :(得分:0)
您是否可以使用您的PUT预签名网址上传而无需担心权限,但是立即使用GET方法创建另一个预签名网址并且无限期到期,并向观众提供 ?
答案 6 :(得分:-1)
您使用的是AWS Node.js官方SDK吗? http://aws.amazon.com/sdkfornodejs/
以下是我使用它的方式......
var data = {
Bucket: "bucket-xyz",
Key: "uploads/" + filename,
Body: buffer,
ACL: "public-read",
ContentType: mime.lookup(filename)
};
s3.putObject(data, callback);
我上传的文件是公共可读的。希望它有所帮助。