python 为 Amazon CloudFront 创建签名 URL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2573919/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-04 00:57:30  来源:igfitidea点击:

Creating Signed URLs for Amazon CloudFront

pythoncdnamazon-cloudfront

提问by Zack

Short version: How do I make signed URLs "on-demand" to mimic Nginx's X-Accel-Redirect behavior (i.e. protecting downloads) with Amazon CloudFront/S3 using Python.

简短版本:如何使用 Python 使用 Amazon CloudFront/S3“按需”制作签名 URL 以模拟 Nginx 的 X-Accel-重定向行为(即保护下载)。

I've got a Django server up and running with an Nginx front-end. I've been getting hammered with requests to it and recently had to install it as a TornadoWSGI application to prevent it from crashing in FastCGI mode.

我已经启动了一个 Django 服务器并运行了一个 Nginx 前端。我一直受到对它的请求的打击,最近不得不将它安装为TornadoWSGI 应用程序,以防止它在 FastCGI 模式下崩溃。

Now I'm having an issue with my server getting bogged down (i.e. most of its bandwidth is being used up) due to too many requests for media being made to it, I've been looking into CDNs and I believe Amazon CloudFront/S3 would be the proper solution for me.

现在我的服务器出现问题,由于对它的媒体请求太多,我的服务器陷入困境(即它的大部分带宽被用完),我一直在研究 CDN,我相信 Amazon CloudFront/S3对我来说将是正确的解决方案。

I've been using Nginx's X-Accel-Redirect header to protect the files from unauthorized downloading, but I don't have that ability with CloudFront/S3--however they do offer signed URLs. I'm no Python expert by far and definitely don't know how to create a Signed URL properly, so I was hoping someone would have a link for how to make these URLs "on-demand" or would be willing to explain how to here, it would be greatly appreciated.

我一直在使用 Nginx 的 X-Accel-Redirect 标头来保护文件免遭未经授权的下载,但我在 CloudFront/S3 中没有这种能力——但是它们确实提供了签名 URL。到目前为止,我不是 Python 专家,绝对不知道如何正确创建签名 URL,所以我希望有人能提供一个链接,了解如何“按需”制作这些 URL,或者愿意解释如何在此,将不胜感激。

Also, is this the proper solution, even? I'm not too familiar with CDNs, is there a CDN that would be better suited for this?

另外,这是正确的解决方案吗?我对 CDN 不太熟悉,有没有更适合这个的 CDN?

回答by secretmike

Amazon CloudFront Signed URLswork differently than Amazon S3 signed URLs. CloudFront uses RSA signatures based on a separate CloudFront keypair which you have to set up in your Amazon Account Credentials page. Here's some code to actually generate a time-limited URL in Python using the M2Cryptolibrary:

Amazon CloudFront 签名 URL 的工作方式与 Amazon S3签名 URL不同。CloudFront 使用基于单独 CloudFront 密钥对的 RSA 签名,您必须在您的 Amazon Account Credentials 页面中设置该密钥对。以下是使用M2Crypto库在 Python 中实际生成限时 URL 的一些代码:

Create a keypair for CloudFront

为 CloudFront 创建密钥对

I think the only way to do this is through Amazon's web site. Go into your AWS "Account" page and click on the "Security Credentials" link. Click on the "Key Pairs" tab then click "Create a New Key Pair". This will generate a new key pair for you and automatically download a private key file (pk-xxxxxxxxx.pem). Keep the key file safe and private. Also note down the "Key Pair ID" from amazon as we will need it in the next step.

我认为唯一的方法是通过亚马逊的网站。进入您的 AWS“帐户”页面,然后单击“安全凭证”链接。单击“密钥对”选项卡,然后单击“创建新的密钥对”。这将为您生成一个新的密钥对并自动下载一个私钥文件(pk-xxxxxxxxx.pem)。保持密钥文件的安全和私密。还要记下来自亚马逊的“密钥对 ID”,因为我们将在下一步中需要它。

Generate some URLs in Python

在 Python 中生成一些 URL

As of boto version 2.0 there does not seem to be any support for generating signed CloudFront URLs. Python does not include RSA encryption routines in the standard library so we will have to use an additional library. I've used M2Crypto in this example.

从 boto 2.0 版开始,似乎不支持生成签名的 CloudFront URL。Python 的标准库中不包含 RSA 加密例程,因此我们将不得不使用额外的库。我在这个例子中使用了 M2Crypto。

For a non-streaming distribution, you must use the full cloudfront URL as the resource, however for streaming we only use the object name of the video file. See the code below for a full example of generating a URL which only lasts for 5 minutes.

对于非流式分发,您必须使用完整的 cloudfront URL 作为资源,但是对于流式传输,我们仅使用视频文件的对象名称。有关生成仅持续 5 分钟的 URL 的完整示例,请参阅下面的代码。

This code is based loosely on the PHP example code provided by Amazon in the CloudFront documentation.

此代码松散地基于 Amazon 在 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)

Make sure that you set up your distribution with a TrustedSigners parameter set to the account holding your keypair (or "Self" if it's your own account)

确保您使用 TrustedSigners 参数设置您的分发,该参数设置为持有您的密钥对的帐户(如果是您自己的帐户,则为“Self”)

See Getting started with secure AWS CloudFront streaming with Pythonfor a fully worked example on setting this up for streaming with Python

请参阅使用 Python 开始使用安全的 AWS CloudFront 流式传输以获取有关设置此功能以使用 Python 进行流式传输的完整示例

回答by RayLuo

This feature is now already supported in Botocore, which is the underlying library of Boto3, the latest official AWS SDK for Python. (The following sample requires the installation of the rsa package, but you can use other RSA package too, just define your own "normalized RSA signer".)

Botocore现已支持此功能,BotocoreBoto3(适用于 Python 的最新官方 AWS 开发工具包)的底层库。(以下示例需要安装 rsa 包,但您也可以使用其他 RSA 包,只需定义您自己的“规范化 RSA 签名者”即可。)

The usage looks like this:

用法如下所示:

    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)

Disclaimer: I am the author of that PR.

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

回答by Steffen Opel

As many have commented already, the initially accepted answerdoesn't apply to Amazon CloudFrontin fact, insofar Serving Private Content through CloudFrontrequires the use of dedicated CloudFront Signed URLs- accordingly secretmike's answerhas been correct, but it is meanwhile outdated after he himself took the time and Added support for generating signed URLs for CloudFront(thanks much for this!).

正如许多人已经评论过的那样,最初接受的答案实际上并不适用于Amazon CloudFront,因为通过 CloudFront 提供私有内容需要使用专用的CloudFront 签名 URL- 因此,secretmike 的答案是正确的,但同时在他本人之后已经过时花时间并添加了对为 CloudFront 生成签名 URL 的支持(非常感谢!)。

botonow supports a dedicated create_signed_urlmethod and the former binary dependency M2Crypto has recently been replaced with a pure-Python RSA implementationas well, see Don't use M2Crypto for cloudfront URL signing.

boto现在支持专用的create_signed_url方法,并且之前的二进制依赖项 M2Crypto 最近也被替换为纯 Python RSA 实现,请参阅不要使用 M2Crypto 进行云端 URL 签名

As increasingly common, one can find one or more good usage examples within the related unit tests (see test_signed_urls.py), for example test_canned_policy(self)- see setUp(self)for the referenced variables self.pk_idand self.pk_str(obviously you'll need your own keys):

随着越来越普遍,人们可以在相关的单元测试中找到一个或多个好的使用示例(请参阅test_signed_urls.py),例如test_canned_policy(self)- 请参阅setUp(self)以获取引用的变量self.pk_idself.pk_str(显然您需要自己的键):

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)

回答by ravi404

secretmike's answer works, but it is better to use rsainstead of M2Crypto.

secretmike的答案的作品,但它是更好地使用rsa,而不是M2Crypto

I used botowhich uses rsa.

我使用botowhich 使用rsa.

import boto
from boto.cloudfront import CloudFrontConnection
from boto.cloudfront.distribution import Distribution

expire_time = int(time.time() +3000)
conn = CloudFrontConnection('ACCESS_KEY_ID', 'SECRET_ACCESS_KEY')

##enter the id or domain name to select a distribution
distribution = Distribution(connection=conn, config=None, domain_name='', id='', last_modified_time=None, status='')
signed_url = distribution.create_signed_url(url='YOUR_URL', keypair_id='YOUR_KEYPAIR_ID_example-APKAIAZVIO4BQ',expire_time=expire_time,private_key_file="YOUR_PRIVATE_KEY_FILE_LOCATION")

Use the boto documentation

使用 boto documentation

回答by nbari

This is what I use for create a policy so that I can give access to multiple files with the same "signature":

这是我用于创建策略的内容,以便我可以访问具有相同“签名”的多个文件:

import json 
import rsa
import time                                                                                                                                                                           

from base64 import b64encode 

url = "http://your_domain/*"                                                                                                                                                                      
expires = int(time.time() + 3600)

pem = """-----BEGIN RSA PRIVATE KEY-----  
...
-----END RSA PRIVATE KEY-----"""

key_pair_id = 'ABX....'

policy = {}                                                                                                                                                                           
policy['Statement'] = [{}]                                                                                                                                                            
policy['Statement'][0]['Resource'] = url                                                                                                                                              
policy['Statement'][0]['Condition'] = {}                                                                                                                                              
policy['Statement'][0]['Condition']['DateLessThan'] = {}                                                                                                                              
policy['Statement'][0]['Condition']['DateLessThan']['AWS:EpochTime'] = expires                                                                                                        

policy = json.dumps(policy)

private_key = rsa.PrivateKey.load_pkcs1(pem)                                                                                                                                          
signature = b64encode(rsa.sign(str(policy), private_key, 'SHA-1'))

print '?Policy=%s&Signature=%s&Key-Pair-Id=%s' % (b64encode(policy),                                                                                                                             
                                                  signature,                                                                                                                          
                                                  key_pair_id)

I can use it for all files under http://your_domain/*for example:

我可以将它用于以下所有文件http://your_domain/*,例如:

 http://your_domain/image2.png?Policy...
 http://your_domain/image2.png?Policy...
 http://your_domain/file1.json?Policy...

回答by Tan Nguyen

I find simple solutions do not need change s3.generate_urlways,

我发现简单的解决方案不需要改变s3.generate_url方式,

just select your Cloudfront config: Yes, Update bucket policy.

只需选择你的Cloudfront配置:Yes, Update bucket policy

After that change from :

之后从:

https://xxxx.s3.amazonaws.com/hello.png&Signature=sss&Expires=1585008320&AWSAccessKeyId=kkk

to

https://yyy.cloudfront.net/hello.png&Signature=sss&Expires=1585008320&AWSAccessKeyId=kkk

with yyy.cloudfront.netis your CloudFront domain

withyyy.cloudfront.net是您的 CloudFront 域

refer to: https://aws.amazon.com/blogs/developer/accessing-private-content-in-amazon-cloudfront/

参考:https: //aws.amazon.com/blogs/developer/accessing-private-content-in-amazon-cloudfront/

enter image description here

在此处输入图片说明