亚马逊 AWS S3 基于浏览器的上传使用 POST -
Amazon AWS S3 browser-based upload using POST -
我正在构建一个包含文件上传功能的 Web 应用程序。我的目标是启动用户直接上传到 S3 存储桶。该策略是预先签署一个 POST 请求,该请求将作为表单提交。
障碍是一个 SignatureDoesNotMatch
错误 - 据我所知,我已经遵守了文档,并探索了很多选项,但仍然无法解决。我能够生成预先签名的下载链接。
引用:
boto3 generate_presigned_post
reference
生成签名请求:
def s3_upload_creds(name, user):
s3 = boto3.client('s3')
key = '${filename}'
region = 'us-east-1'
date_short = datetime.datetime.utcnow().strftime('%Y%m%d')
date_long = datetime.datetime.utcnow().strftime('%Y%m%dT000000Z')
fields = {
'acl': 'private',
'date': date_short,
'region': region,
'x-amz-algorithm': 'AWS4-HMAC-SHA256',
'x-amz-date': date_long
}
return s3.generate_presigned_post(
Bucket = 'leasy',
Fields = fields,
Key = key,
Conditions = [
{'acl': 'private'},
{'x-amz-algorithm': 'AWS4-HMAC-SHA256'},
{'x-amz-credential': '/'.join(['AKI--snip--', date_short, region, 's3', 'aws4_request'])},
{'x-amz-date': date_long}
]
)
上传表格(上面填写了fields
):
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
{{ creds }}
<form action="{{ creds.url }}" method="post" enctype="multipart/form-data">
Key to upload:
<input type="input" name="key" value="${filename}" /><br />
<input type="input" name="acl" value="{{ creds.fields.acl }}" />
<input type="hidden" name="Policy" value="{{ creds.fields.policy }}" />
<input type="text" name="X-Amz-Algorithm" value="{{ creds.fields['x-amz-algorithm'] }}" />
<input type="input" name="X-Amz-Credential" value="{{ creds.fields.AWSAccessKeyId }}/{{ creds.fields.date }}/us-east-1/s3/aws4_request" />
<input type="input" name="X-Amz-Date" value="{{ creds.fields['x-amz-date'] }}" />
<input type="input" name="X-Amz-Signature" value="{{ creds.fields.signature }}" />
File:
<input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
回复的相关部分:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
<AWSAccessKeyId>AKI--snip--</AWSAccessKeyId>
<StringToSign>
eyJjb25kaXRpb25zIjogW3siYWNsIjogInByaXZhdGUifSwgeyJ4LWFtei1hbGdvcml0aG0iOiAiQVdTNC1ITUFDLVNIQTI1NiJ9LCB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlDVjRNVlBUUlFHU1lLV1EvMjAxNTEyMTgvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LCB7IngtYW16LWRhdGUiOiAiMjAxNTEyMThUMDAwMDAwWiJ9LCB7ImJ1Y2tldCI6ICJsZWFzeSJ9LCBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAiIl1dLCAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTE4VDA1OjEwOjU2WiJ9
</StringToSign>
<SignatureProvided>wDOjsBRc0iIW7JNtz/4GHgfvKaU=</SignatureProvided>
Base64 解码 StringToSign
在上面的错误:
{u'conditions': [{u'acl': u'private'},
{u'x-amz-algorithm': u'AWS4-HMAC-SHA256'},
{u'x-amz-credential': u'AKI--snip--/20151218/us-east-1/s3/aws4_request'},
{u'x-amz-date': u'20151218T000000Z'},
{u'bucket': u'leasy'},
[u'starts-with', u'$key', u'']],
u'expiration': u'2015-12-18T04:59:32Z'}
找到解决方案:必须显式配置 s3 客户端以使用亚马逊的新签名 v4。发生错误是因为它默认为旧版本,导致不匹配。有点面子 - 当时这还没有写在 boto3 文档中,尽管亚马逊的人说应该很快。
该方法得到了简化,因为它现在 returns 正是所需的字段:
def s3_upload_creds(name):
BUCKET = 'mybucket'
REGION = 'us-west-1'
s3 = boto3.client('s3', region_name=REGION, config=Config(signature_version='s3v4'))
key = '${filename}'
return s3.generate_presigned_post(
Bucket = BUCKET,
Key = key
)
这意味着可以轻松生成表单:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
{{ creds }}
<form action="https://mybucket.s3.amazonaws.com" method="post" enctype="multipart/form-data">
{% for key, value in creds.fields.items() %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endfor %}
File:
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
干杯
自上次回复以来已经有几年了,但我在过去一两天里一直坚持这个问题,所以我会与可能有帮助的任何人分享我的经验。
我收到错误:“403:您提供的 AWS 访问密钥 ID 在我们的记录中不存在” 当我尝试通过我的预签名上传到 s3 存储桶时url.
我能够使用 服务器端 代码成功生成与上面类似的预签名 url:
signed_url_dict = self.s3_client.generate_presigned_post(
self.bucket_name,
object_name,
ExpiresIn=300
这返回了具有以下结构的字典:
{
url: "https://___",
fields: {
key: "___",
AWSAccesKeyId: "___",
x-amz-security-token: "___",
policy: "___",
signature: "___"
}
}
这导致 2019 年浏览器端 javascript 的情况有所不同,其中所需的表单输入似乎已发生变化。我没有像 OP 那样设置表单,而是必须创建如下所示的表单:
<form action="https://pipeline-poc-ed.s3.amazonaws.com/" method="post" enctype="multipart/form-data" name="upload_form">
<!-- Copy ALL of the 'fields' key:values returned by S3Client.generate_presigned_post() -->
<input type="hidden" name="key" value="___" />
<input type="hidden" name="AWSAccessKeyId" value="___" />
<input type="hidden" name="policy" value="___"/>
<input type="hidden" name="signature" value="___" />
<input type="hidden" name="x-amz-security-token" value="___" />
File:
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
我的错误 是我按照 boto3 1.9.138 docs 中的示例进行操作,而在表格中遗漏了 "x-amz-security-token",事实证明这是非常必要的。可能部分的粗心疏忽,但希望这会帮助其他人。
编辑: 我上面的结果基于 N. Virginia Lambda 函数。当我 运行 generate_presigned_post(...)
在俄亥俄州(包含我的桶的区域)时,我得到类似于 OP 的结果:
{
"url": "https://__",
"fields": {
"key": "___",
"x-amz-algorithm": "___",
"x-amz-credential": "___",
"x-amz-date": "___",
"x-amz-security-token": "___",
"policy": "___",
"x-amz-signature": "___"
}
}
也许函数的结果是区域特定的?
在我的例子中,我生成了一个 Base64 编码的表单。
问题是由于 Firefox 固有地将策略和安全令牌值编码为基于其的 Base64 编码。
因此存在双重编码,因此签名不符合要求。
我正在构建一个包含文件上传功能的 Web 应用程序。我的目标是启动用户直接上传到 S3 存储桶。该策略是预先签署一个 POST 请求,该请求将作为表单提交。
障碍是一个 SignatureDoesNotMatch
错误 - 据我所知,我已经遵守了文档,并探索了很多选项,但仍然无法解决。我能够生成预先签名的下载链接。
引用:
boto3 generate_presigned_post
reference
生成签名请求:
def s3_upload_creds(name, user):
s3 = boto3.client('s3')
key = '${filename}'
region = 'us-east-1'
date_short = datetime.datetime.utcnow().strftime('%Y%m%d')
date_long = datetime.datetime.utcnow().strftime('%Y%m%dT000000Z')
fields = {
'acl': 'private',
'date': date_short,
'region': region,
'x-amz-algorithm': 'AWS4-HMAC-SHA256',
'x-amz-date': date_long
}
return s3.generate_presigned_post(
Bucket = 'leasy',
Fields = fields,
Key = key,
Conditions = [
{'acl': 'private'},
{'x-amz-algorithm': 'AWS4-HMAC-SHA256'},
{'x-amz-credential': '/'.join(['AKI--snip--', date_short, region, 's3', 'aws4_request'])},
{'x-amz-date': date_long}
]
)
上传表格(上面填写了fields
):
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
{{ creds }}
<form action="{{ creds.url }}" method="post" enctype="multipart/form-data">
Key to upload:
<input type="input" name="key" value="${filename}" /><br />
<input type="input" name="acl" value="{{ creds.fields.acl }}" />
<input type="hidden" name="Policy" value="{{ creds.fields.policy }}" />
<input type="text" name="X-Amz-Algorithm" value="{{ creds.fields['x-amz-algorithm'] }}" />
<input type="input" name="X-Amz-Credential" value="{{ creds.fields.AWSAccessKeyId }}/{{ creds.fields.date }}/us-east-1/s3/aws4_request" />
<input type="input" name="X-Amz-Date" value="{{ creds.fields['x-amz-date'] }}" />
<input type="input" name="X-Amz-Signature" value="{{ creds.fields.signature }}" />
File:
<input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
回复的相关部分:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
<AWSAccessKeyId>AKI--snip--</AWSAccessKeyId>
<StringToSign>
eyJjb25kaXRpb25zIjogW3siYWNsIjogInByaXZhdGUifSwgeyJ4LWFtei1hbGdvcml0aG0iOiAiQVdTNC1ITUFDLVNIQTI1NiJ9LCB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlDVjRNVlBUUlFHU1lLV1EvMjAxNTEyMTgvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LCB7IngtYW16LWRhdGUiOiAiMjAxNTEyMThUMDAwMDAwWiJ9LCB7ImJ1Y2tldCI6ICJsZWFzeSJ9LCBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAiIl1dLCAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTE4VDA1OjEwOjU2WiJ9
</StringToSign>
<SignatureProvided>wDOjsBRc0iIW7JNtz/4GHgfvKaU=</SignatureProvided>
Base64 解码 StringToSign
在上面的错误:
{u'conditions': [{u'acl': u'private'},
{u'x-amz-algorithm': u'AWS4-HMAC-SHA256'},
{u'x-amz-credential': u'AKI--snip--/20151218/us-east-1/s3/aws4_request'},
{u'x-amz-date': u'20151218T000000Z'},
{u'bucket': u'leasy'},
[u'starts-with', u'$key', u'']],
u'expiration': u'2015-12-18T04:59:32Z'}
找到解决方案:必须显式配置 s3 客户端以使用亚马逊的新签名 v4。发生错误是因为它默认为旧版本,导致不匹配。有点面子 - 当时这还没有写在 boto3 文档中,尽管亚马逊的人说应该很快。
该方法得到了简化,因为它现在 returns 正是所需的字段:
def s3_upload_creds(name):
BUCKET = 'mybucket'
REGION = 'us-west-1'
s3 = boto3.client('s3', region_name=REGION, config=Config(signature_version='s3v4'))
key = '${filename}'
return s3.generate_presigned_post(
Bucket = BUCKET,
Key = key
)
这意味着可以轻松生成表单:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
{{ creds }}
<form action="https://mybucket.s3.amazonaws.com" method="post" enctype="multipart/form-data">
{% for key, value in creds.fields.items() %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endfor %}
File:
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
干杯
自上次回复以来已经有几年了,但我在过去一两天里一直坚持这个问题,所以我会与可能有帮助的任何人分享我的经验。
我收到错误:“403:您提供的 AWS 访问密钥 ID 在我们的记录中不存在” 当我尝试通过我的预签名上传到 s3 存储桶时url.
我能够使用 服务器端 代码成功生成与上面类似的预签名 url:
signed_url_dict = self.s3_client.generate_presigned_post(
self.bucket_name,
object_name,
ExpiresIn=300
这返回了具有以下结构的字典:
{
url: "https://___",
fields: {
key: "___",
AWSAccesKeyId: "___",
x-amz-security-token: "___",
policy: "___",
signature: "___"
}
}
这导致 2019 年浏览器端 javascript 的情况有所不同,其中所需的表单输入似乎已发生变化。我没有像 OP 那样设置表单,而是必须创建如下所示的表单:
<form action="https://pipeline-poc-ed.s3.amazonaws.com/" method="post" enctype="multipart/form-data" name="upload_form">
<!-- Copy ALL of the 'fields' key:values returned by S3Client.generate_presigned_post() -->
<input type="hidden" name="key" value="___" />
<input type="hidden" name="AWSAccessKeyId" value="___" />
<input type="hidden" name="policy" value="___"/>
<input type="hidden" name="signature" value="___" />
<input type="hidden" name="x-amz-security-token" value="___" />
File:
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
我的错误 是我按照 boto3 1.9.138 docs 中的示例进行操作,而在表格中遗漏了 "x-amz-security-token",事实证明这是非常必要的。可能部分的粗心疏忽,但希望这会帮助其他人。
编辑: 我上面的结果基于 N. Virginia Lambda 函数。当我 运行 generate_presigned_post(...)
在俄亥俄州(包含我的桶的区域)时,我得到类似于 OP 的结果:
{
"url": "https://__",
"fields": {
"key": "___",
"x-amz-algorithm": "___",
"x-amz-credential": "___",
"x-amz-date": "___",
"x-amz-security-token": "___",
"policy": "___",
"x-amz-signature": "___"
}
}
也许函数的结果是区域特定的?
在我的例子中,我生成了一个 Base64 编码的表单。
问题是由于 Firefox 固有地将策略和安全令牌值编码为基于其的 Base64 编码。
因此存在双重编码,因此签名不符合要求。