我在使用 Python 3.7 为 S3 对象生成签名 URL 时遇到问题。具体来说,键中带有空格的对象的 URL 会导致“访问被拒绝”错误,而没有空格的对象的 URL 通常工作正常。但是,并非所有不带空格的对象都能正常工作,带空格的对象始终会失败。
from datetime import datetime ,timedelta
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from botocore.signers import CloudFrontSigner
class SecuringCloudFront:
def __init__(self):
pass
def rsa_signer(self, message):
try:
with open('private_key.pem', 'rb') as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
backend=default_backend()
)
return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
except Exception as e:
print(f"Error loading or signing with private key: {e}")
return None
def get_document_images_from_s3(self, images_keys, images_to_get=None):
"""
Description: creates signed CloudFront URLs for document images
Attributes:
- images_keys: list of keys
- images_to_get: list of images index
Result: list of URLs
"""
CLOUDFRONT_URL = "https://***********.cloudfront.net"
KEY_PAIR_ID = "********" #id of stored key
images_urls = []
keys = (
images_to_get
if images_to_get is not None
else list(range(len(images_keys)))
)
for key in keys:
try:
if key >= len(images_keys) or len(images_keys[key]["name"]) == 0:
continue
s3_key = images_keys[key]["name"]
url = f"{CLOUDFRONT_URL}/{s3_key}"
expire_date = datetime.utcnow() + timedelta(hours=24)
cloudfront_signer = CloudFrontSigner(KEY_PAIR_ID, self.rsa_signer)
signed_url = cloudfront_signer.generate_presigned_url(
url, date_less_than=expire_date
)
url = {"url": signed_url} if signed_url else {"url": ""}
except Exception as e:
url = {"url": ""}
print(f"Error generating signed URL: {e}")
images_urls.append(url)
return images_urls
if __name__ == "__main__":
securing_cloud_front = SecuringCloudFront()
images_keys = [
{"name": "imageIns3.jpg"}
]
signed_urls = securing_cloud_front.get_document_images_from_s3(images_keys)
print("Signed URLs:", signed_urls)
用 %20 和 + 替换空格:
我尝试用 URL 中的 %20 和 + 替换对象键中的空格。此方法没有解决问题,我仍然收到“访问被拒绝”错误。 集成自定义策略:
我尝试集成用于签名 URL 生成的自定义策略,但无法理解如何正确应用它。尽管进行了各种尝试,我仍无法使用此方法解决问题。
其他信息:当尝试直接从 s3 打开无法从 CloudFront 获取有效 URL 的对象时,该对象被归类为“jpg_files”。从 CloudFront 成功检索到的对象被分类为“jpg_images”
当然,我可以帮助解决这个问题。在为带有空格的 S3 对象生成签名的 CloudFront URL 时遇到的问题很常见。以下是解决方案和需要检查的事项的细分:
理解问题
观察到的行为(有些对象有效而有些无效)表明问题不在于空格本身,而在于 S3 存储桶中对象的权限或元数据不一致。 让我们逐步解决这个问题。
逐步调试和解决方案
- S3 存储桶策略验证: 确保的 S3 存储桶策略明确允许来自的 CloudFront 发行版的访问。以下是一个示例策略:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
-
替换:
-
your-bucket-name
:的 S3 存储桶的实际名称。 -
如果使用的是自定义来源身份,请使用以下方法更新
Principal
:json "Principal": { "AWS": "arn:aws:iam::your-account-id:user/your-cloudfront-origin-identity-username" }
-
-
CloudFront 发行版配置:
-
来源域名称: 在的 CloudFront 发行版配置中,请仔细检查来源域名称是否指向的 S3 存储桶(例如,
your-bucket-name.s3.amazonaws.com
)。 -
来源访问身份: 为的 CloudFront 发行版创建一个或使用现有的来源访问身份 (OAI)。这允许 CloudFront 代表访问的 S3 存储桶。 不要使用的 AWS 根用户或个人用户凭据。
-
对象权限:
-
一致性: 确保所有对象(带有和不带有空格的对象)都具有与其关联的 一致 权限。理想情况下,它们应该继承自存储桶策略,或者应该对每个对象设置明确的权限。
-
直接授予: 如果需要更精细的控制,请使用访问控制列表 (ACL) 或存储桶策略直接向 CloudFront OAI 授予对这些对象的权限。
-
URL 编码:
-
是对的,空格在 URL 中应正确编码为
%20
。+
通常用于替换 URL 查询字符串中的空格,而不是路径本身。 -
Boto3 处理: 好消息是,Boto3 库(正在使用的库)通常会自动为处理 URL 编码。因此,在大多数情况下,无需手动执行此操作。
-
代码审查:
-
的代码片段看起来正确,因为它使用了
CloudFrontSigner
,它应该可以处理 URL 签名过程。
故障排除技巧
- 逐步测试: 首先尝试一个没有空格的简单对象名称。一旦有效,请逐步引入更复杂的对象名称(例如,带有空格的对象名称)。
- CloudFront 日志: 启用 CloudFront 日志以获取有关失败请求的详细见解,包括返回的状态代码以及可能有助于查明问题的任何错误消息。
- S3 访问日志: 同样,启用 S3 访问日志以查看 CloudFront 是否正在访问预期对象,以及是否有任何授权错误。
如果在执行这些步骤后仍然遇到问题,请提供以下信息,以便我进一步帮助:
- 来自 CloudFront 日志的相关错误消息(如果有)。
- 使用的特定 CloudFront 发行版配置(省略敏感信息)。
- 受影响 S3 对象的示例对象键(带有空格)。