为了实现 S3 私有内容的访问,可以使用 CloudFront 的签名 URL 功能。具体来说,可以将 S3 设置为私有访问,然后通过 CloudFront 提供的 https + URL 签名功能来实现访问。此外,还可以对签名 URL 进行 IP 和时间限制,以进一步提高访问的安全性。
1、S3 创建 bucket
创建一个 S3 bucket后,为了防止 S3 bucket 的公开访问,需要在权限设置中对其进行屏蔽。具体来说,可以选择阻止所有公共访问权限,以确保只有授权的用户才能够访问该 bucket。这样可以更好地保护存储在 S3 bucket 中的数据安全。
2、CloudFront 创建分配
在 S3 bucket 创建完成后,您可以为其创建一个 CloudFront 分配,以便更加高效地访问其中的内容。具体来说,您可以在 CloudFront 中选择该 S3 bucket,并创建一个新的分配。这将使您能够通过全球分布的边缘节点快速、可靠地访问存储在 S3 bucket 中的数据。
要对刚刚创建的 CloudFront 分配进行设置,可以进入其管理界面,并点击“源”标签页。在这里,您可以选择分配的源并进行编辑,以进一步优化分配的性能和安全性。
要复制策略以便在 S3 bucket 中配置,请在当前页面点击“复制策略”按钮,然后将其应用于所需的 S3 bucket。这个策略将帮助您更好地管理 S3 bucket 中的对象,确保其安全性和一致性。
3、设置 bucket 权限
回到 S3 bucket 的权限页面,在“存储桶策略”下点击“编辑”,将在 CloudFront 中复制的策略粘贴到此处,然后保存即可。这将确保 S3 bucket 受到相应的访问限制,并与 CloudFront 配合工作,为您提供更安全、高效的内容分发服务。
通过上述步骤的设置,S3 bucket 已经被成功限制访问,只能通过 CloudFront 进行代理访问。然而,目前为止所有的 URL 都是公开可见的,这可能会对数据的安全性造成潜在威胁。为了进一步保护数据的安全,需要对每个 URL 进行单独加密。下面将介绍如何进行 URL 加密的操作。
4、创建秘钥
在对 URL 进行加密之前,需要先创建一个秘钥对。创建完成后,您需要将公钥添加到 CloudFront 的秘钥管理中,以便将其用于加密 URL。
成功创建秘钥后,请务必牢记其对应的秘钥 ID。在生成 URL 签名时,秘钥 ID 是必需的参数。
接下来,需要创建一个密钥组,并将刚刚创建的秘钥与该组相关联。在 CloudFront 中配置秘钥时,需要选择该密钥组。这样做的好处是,可以针对不同的 URL 使用不同的签名,进一步增强数据的安全性。
5、限制查看器访问
在完成秘钥和密钥组的创建之后,回到之前创建的 CloudFront 分配页面。然后,点击“行为”标签页,并编辑行为设置。
在编辑页面中,将“限制查看器访问”选项设为“Yes”,并选择“可信授权类型”为“ Trusted key groups (recommended)”。选择刚刚创建的密钥组,点击“保存”即可完成 CloudFront 的配置。
6、编写签名代码
完成上述配置后,下一步是在代码中实现 URL 签名。以下是 Java 代码示例:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// CloudFront 分配的域名,也可以绑定自己的域名。
String distributionDomain = "d30xklqmm46leu.cloudfront.net";
// S3 即将被访问的对接
String s3ObjectKey = "avatar/n01582220_6305.jpg";
// 秘钥 ID
String keyPairId = "K1S0R8IKNRE91H";
// 私钥
File privateKeyFile = new File("F:\\data\\cloudfront_ssl\\private_key.der");
// 允许被访问的 IP
String ipRange = "0.0.0.0/0";
// URL 签名的有效时间
Date dateLessThan = DateUtils.parseISO8601Date("2024-11-14T22:20:00.000Z");
Date dateGreaterThan = DateUtils.parseISO8601Date("2023-03-20T22:20:00.000Z");
String signedUrl = CloudFrontUrlSigner.getSignedURLWithCustomPolicy(
SignerUtils.Protocol.https, distributionDomain, privateKeyFile,
s3ObjectKey, keyPairId, dateLessThan,
dateGreaterThan, ipRange);
System.out.println(signedUrl);
7、测试
运行代码后,将会输出以下结果:
https://d30xklqmm46leu.cloudfront.net/avatar/n01582220_6305.jpg?Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vZDMweGtscW1tNDZsZXUuY2xvdWRmcm9udC5uZXQvYXZhdGFyL24wMTU4MjIyMF82MzA1LmpwZyIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTczMTYyMjgwMH0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifSwiRGF0ZUdyZWF0ZXJUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE2NzkzNTA4MDB9fX1dfQ__&Signature=CArbpEYmHU3XKLQEFHQmKCbQSERnCRovQeto6JjVV0-58gC2oY7uu0~B2OHJ3NHT72ZBDcI~2JSMZOhuy7L3qKcVS2bVfr62BOT3KajbZADClB2F1v-F0MumqOGyJFJqCptntkG1gZMahvBEXtOUi5d1bcua7u04yQNNwky7o4PLKqzMFIB~fp1Ub0IYO0fPdcC1iS8eQIKh6ehMiX2iLBtSqWz-KtJwvadWArkf~PIm6RHHycTj8U95ZrD-DV69Qh0pE2sJan-g3YhyqEmyN7Nq1GRK8B9X-yMoRwEdX68jemviOnfSOmAUZtrhH6-Lc-BNriV6knUHzRmokyo5Fg__&Key-Pair-Id=K1S0R8IKNRE91H
需要注意的是,签名后的 URL 在使用 http 和 https 协议时需要分别签名,不能通用。
8、附录
8.1、生成秘钥
使用 OpenSSL 生成长度为 2048 位的 RSA 密钥对,并将其保存到名为 private_key.pem
的文件中。
openssl genrsa -out private_key.pem 2048
上面生成的文件同时包含公有密钥和私有密钥。从名为 private_key.pem
的文件中提取公有密钥。
openssl rsa -pubout -in private_key.pem -out public_key.pem
针对 Java 不能以默认 PEM 格式使用密钥对中的私有密钥来创建签名,要重新设置秘钥对格式为 DER。
openssl pkcs8 -topk8 -nocrypt -in private_key.pem -inform PEM -out private_key.der -outform DER
8.2、参考资料