有没有办法使用 AWS 签名 URL 执行分块上传?

6 投票
2 回答
1803 浏览
提问于 2025-04-18 09:53

我正在为一个服务编写客户端,这个服务提供一个签名的链接用于文件上传。对于小文件上传,这个链接工作得很好,但对于大文件上传,就需要使用分块上传,这样才能更顺利。

授权文档建议我可以在链接中或者通过授权头使用提供的签名和访问密钥ID。我尝试用头的方式来开始分块上传,但却收到了拒绝访问的错误。当我使用查询字符串的方式时,又出现了不允许的方法错误(在这种情况下是POST)。

我使用boto来生成这个链接。比如:

import boto

c = boto.connect_s3()
bucket = c.get_bucket('my-bucket')
key = boto.s3.key.Key(bucket, 'my-big-file.gz')
signed_url = key.generate_url(60 * 60, 'POST')  # expires in an hour

然后在尝试使用这个签名链接开始分块上传时,我做了以下操作:

import requests

url = signed_url + '&uploads'
resp = requests.post(url)

结果返回了不允许的方法错误。

这个策略可行吗?有没有更好的方法来为特定资源提供有限的凭证,以便允许大文件的分块上传?

更新

我找到了一些更具体的错误信息,这让我觉得这可能行不通。不幸的是,我收到了403错误,提示签名与请求不匹配。

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>SignatureDoesNotMatch</Code>
  <Message>The request signature we calculated does not match the signature you
  provided. Check your key and signing method.</Message>
  <StringToSignBytes>.../StringToSignBytes>
  <RequestId>...</RequestId>
  <HostId>...</HostId>
  <SignatureProvided>...</SignatureProvided>
  <StringToSign>POST


  1402941975
  /my-sandbox/test-mp-upload.txt?uploads</StringToSign>
  <AWSAccessKeyId>...</AWSAccessKeyId>
</Error>

这让我觉得我可能无法使用这个签名链接,因为签名不匹配。

更新

我决定使用签名链接进行分块上传并不合理。虽然我怀疑技术上是可行的,但实际上并不方便。原因是签名链接要求URL、头信息和请求方法都必须完全匹配才能正常工作。而分块上传需要初始化上传、上传每一部分并最终完成(或取消)上传,这样每一步都生成链接会非常麻烦。

相反,我发现可以创建一个联合令牌,提供对存储桶中特定密钥的读写访问权限。这样做更实用也更简单,因为我可以立即像拥有凭证一样使用boto。

2 个回答

4

这里有一些代码,可以帮助下一个想要做这个的人节省时间:

import json
from uuid import uuid4

import boto3


def get_upload_credentials_for(bucket, key, username):
    arn = 'arn:aws:s3:::%s/%s' % (bucket, key)
    policy = {"Version": "2012-10-17",
              "Statement": [{
                  "Sid": "Stmt1",
                  "Effect": "Allow",
                  "Action": ["s3:PutObject"],
                  "Resource": [arn],
              }]}
    client = boto3.client('sts')
    response = client.get_federation_token(
        Name=username, Policy=json.dumps(policy))
    return response['Credentials']


def client_from_credentials(service, credentials):
    return boto3.client(
        service,
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken'],
    )


def example():
    bucket = 'mybucket'
    filename = '/path/to/file'

    key = uuid4().hex
    print(key)

    prefix = 'tmp_upload_'
    username = prefix + key[:32 - len(prefix)]
    print(username)
    assert len(username) <= 32  # required by the AWS API

    credentials = get_upload_credentials_for(bucket, key, username)
    client = client_from_credentials('s3', credentials)
    client.upload_file(filename, bucket, key)
    client.upload_file(filename, bucket, key + 'bob')  # fails


example()
2

为了回答我自己的问题,最好的选择是使用安全令牌服务来生成一组临时的凭证。可以提供一个策略描述,以限制这些凭证只能用于特定的存储桶和密钥。

撰写回答