为亚马逊CloudFront创建签名URL

33 投票
6 回答
29341 浏览
提问于 2025-04-15 21:13

简短版本:我想知道如何用Python制作“按需”生成的签名网址,以模仿Nginx的X-Accel-Redirect功能(也就是保护下载)来使用Amazon CloudFront/S3。

我已经搭建了一个Django服务器,并且前面有一个Nginx作为代理。最近,我的服务器请求量很大,导致我不得不把它安装成一个Tornado WSGI应用,以防止在FastCGI模式下崩溃。

现在,我遇到了一个问题,就是服务器因为请求太多而变得很慢(也就是说,大部分带宽都被占用了),我正在考虑使用CDN,觉得Amazon CloudFront/S3可能是个合适的解决方案。

我之前用Nginx的X-Accel-Redirect头来保护文件,防止未经授权的下载,但在CloudFront/S3上我没有这个功能。不过,他们提供了签名网址。我对Python不是很精通,也不知道怎么正确地创建签名网址,所以希望有人能给我一个链接,或者愿意在这里解释一下怎么做,这样我会非常感激。

另外,这样做是正确的解决方案吗?我对CDN不太了解,是否有其他更合适的CDN可以选择?

6 个回答

16

很多人已经评论过,最开始被接受的答案其实不适用于Amazon CloudFront。实际上,通过CloudFront提供私有内容需要使用专门的CloudFront签名URL。因此,secretmike的回答是正确的,但由于他自己花时间增加了对生成CloudFront签名URL的支持,所以现在这个信息有些过时了(非常感谢他!)。

现在,boto支持一个专门的create_signed_url方法,之前的二进制依赖M2Crypto最近被一个纯Python的RSA实现所替代,具体可以查看不要使用M2Crypto来签名CloudFront URL

现在越来越常见的是,你可以在相关的单元测试中找到一个或多个好的使用示例(见test_signed_urls.py),例如test_canned_policy(self) - 参考的变量self.pk_idself.pk_str可以在setUp(self)中找到(显然你需要自己的密钥):

def test_canned_policy(self):
    """
    Generate signed url from the Example Canned Policy in Amazon's
    documentation.
    """
    url = "http://d604721fxaaqy9.cloudfront.net/horizon.jpg?large=yes&license=yes"
    expire_time = 1258237200
    expected_url = "http://example.com/" # replaced for brevity
    signed_url = self.dist.create_signed_url(
        url, self.pk_id, expire_time, private_key_string=self.pk_str)
    # self.assertEqual(expected_url, signed_url)
30

这个功能现在已经在 Botocore 中得到支持了。Botocore 是 Boto3 的基础库,而 Boto3 是最新的官方 AWS Python 开发工具包。(下面的示例需要安装 rsa 这个包,不过你也可以使用其他的 RSA 包,只要定义你自己的“标准化 RSA 签名器”就可以了。)

使用方法如下:

    from botocore.signers import CloudFrontSigner
    # First you create a cloudfront signer based on a normalized RSA signer::
    import rsa
    def rsa_signer(message):
        private_key = open('private_key.pem', 'r').read()
        return rsa.sign(
            message,
            rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')),
            'SHA-1')  # CloudFront requires SHA-1 hash
    cf_signer = CloudFrontSigner(key_id, rsa_signer)

    # To sign with a canned policy::
    signed_url = cf_signer.generate_presigned_url(
        url, date_less_than=datetime(2015, 12, 1))

    # To sign with a custom policy::
    signed_url = cf_signer.generate_presigned_url(url, policy=my_policy)

免责声明:我是那个 PR 的作者。

36

亚马逊 CloudFront 的签名 URL 和亚马逊 S3 的签名 URL 工作方式不一样。CloudFront 使用基于 RSA 签名的独立密钥对,你需要在亚马逊账户的凭证页面上进行设置。下面是一些用 M2Crypto 库在 Python 中生成限时 URL 的代码:

为 CloudFront 创建一个密钥对

我认为唯一的方法是在亚马逊的网站上进行。进入你的 AWS “账户”页面,点击“安全凭证”链接。然后点击“密钥对”标签,再点击“创建新的密钥对”。这会为你生成一个新的密钥对,并自动下载一个私钥文件(pk-xxxxxxxxx.pem)。请妥善保管这个密钥文件,确保它是安全和私密的。同时记下亚马逊的“密钥对 ID”,因为在下一步中我们会用到它。

在 Python 中生成一些 URL

从 boto 版本 2.0 开始,似乎没有支持生成签名的 CloudFront URL。Python 的标准库中没有 RSA 加密的功能,所以我们需要使用额外的库。在这个例子中,我使用了 M2Crypto。

对于非流媒体分发,你必须使用完整的 CloudFront URL 作为资源,但对于流媒体,我们只需要使用视频文件的对象名称。下面的代码展示了一个生成仅持续 5 分钟的 URL 的完整示例。

这段代码大致基于亚马逊在 CloudFront 文档中提供的 PHP 示例代码。

from M2Crypto import EVP
import base64
import time

def aws_url_base64_encode(msg):
    msg_base64 = base64.b64encode(msg)
    msg_base64 = msg_base64.replace('+', '-')
    msg_base64 = msg_base64.replace('=', '_')
    msg_base64 = msg_base64.replace('/', '~')
    return msg_base64

def sign_string(message, priv_key_string):
    key = EVP.load_key_string(priv_key_string)
    key.reset_context(md='sha1')
    key.sign_init()
    key.sign_update(message)
    signature = key.sign_final()
    return signature

def create_url(url, encoded_signature, key_pair_id, expires):
    signed_url = "%(url)s?Expires=%(expires)s&Signature=%(encoded_signature)s&Key-Pair-Id=%(key_pair_id)s" % {
            'url':url,
            'expires':expires,
            'encoded_signature':encoded_signature,
            'key_pair_id':key_pair_id,
            }
    return signed_url

def get_canned_policy_url(url, priv_key_string, key_pair_id, expires):
    #we manually construct this policy string to ensure formatting matches signature
    canned_policy = '{"Statement":[{"Resource":"%(url)s","Condition":{"DateLessThan":{"AWS:EpochTime":%(expires)s}}}]}' % {'url':url, 'expires':expires}

    #sign the non-encoded policy
    signature = sign_string(canned_policy, priv_key_string)
    #now base64 encode the signature (URL safe as well)
    encoded_signature = aws_url_base64_encode(signature)

    #combine these into a full url
    signed_url = create_url(url, encoded_signature, key_pair_id, expires);

    return signed_url

def encode_query_param(resource):
    enc = resource
    enc = enc.replace('?', '%3F')
    enc = enc.replace('=', '%3D')
    enc = enc.replace('&', '%26')
    return enc


#Set parameters for URL
key_pair_id = "APKAIAZVIO4BQ" #from the AWS accounts CloudFront tab
priv_key_file = "cloudfront-pk.pem" #your private keypair file
# Use the FULL URL for non-streaming:
resource = "http://34254534.cloudfront.net/video.mp4"
#resource = 'video.mp4' #your resource (just object name for streaming videos)
expires = int(time.time()) + 300 #5 min

#Create the signed URL
priv_key_string = open(priv_key_file).read()
signed_url = get_canned_policy_url(resource, priv_key_string, key_pair_id, expires)

print(signed_url)

#Flash player doesn't like query params so encode them if you're using a streaming distribution
#enc_url = encode_query_param(signed_url)
#print(enc_url)

确保你在分发设置中将 TrustedSigners 参数设置为持有你密钥对的账户(如果是你自己的账户,则设置为“Self”)。

有关如何使用 Python 设置流媒体的完整示例,请查看 使用 Python 开始安全的 AWS CloudFront 流媒体

撰写回答