我在S3中有一个存储桶,我将其链接到CNAME别名。我们现在假设该域名为 media.mycompany.com 。在存储桶中是所有设置为私有的图像文件。然而,他们使用URL签名在我的网站上公开使用。签名的URL可能如下所示:
这样可以正常工作。我在PHP中使用S3辅助库来生成这样的URL。这是该库的标识符:
$Id: S3.php 44 2008-12-23 15:38:38Z don.schonknecht $
我知道它已经老了,但是我依赖于这个库中的很多方法,所以升级并不是一件容易的事,而且如上所述,它对我很有用。以下是此库中的相关方法:
public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) {
$expires = time() + $lifetime;
$uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
$hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
}
在我正常的工作设置中,我会这样称呼这个方法:
$return = $this->s3->getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename,
$timestamp, true, false);
这将返回此帖子前面共享的正确签名的URL,一切都很好。
但是,我现在想生成HTTPS URL,这就是我遇到问题的地方。只需将HTTP添加到当前URL(通过将方法的最后一个参数设置为true)将无效,它将生成如下URL:
这显然不起作用,因为我的SSL证书(由letsencrypt创建)未安装在亚马逊的域上,据我所知,没有办法这样做。
我已经了解了通过SSL访问存储桶的替代网址格式:
这显然适用于某些人,但不适合我,据我所知,这是因为我的桶名中有一个点(。)字符。我无法更改存储桶名称,这会在我的设置中产生很大的影响。
最后,有这种格式:
在这里,我越来越近了。如果我采用有效的非安全URL,并编辑URL以采用此格式,则可行。显示图像。
现在我想让它以自动方式工作,从我之前展示的签名方法开始。我这样称呼它:
$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
$timestamp, true, true);
此处的更改是备用存储桶名称格式,最后一个参数设置为true,表示HTTP。这导致了这样的输出:
如您所见,它的格式与我手动制作的网址格式相同。但不幸的是,我收到了签名错误:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
我很难搞清楚为什么这些签名不正确。我尝试将签名方法的第4个参数设置为true和false,但没有区别。
我错过了什么?
修改
根据迈克尔的回答,我试图在调用S3库之后进行简单的字符串替换,这有效。快速而肮脏的代码:
$return = $this->s3->getAuthenticatedURL("media.mycompany.com", $dir . '/' . $filename, $timestamp, true, true);
$return = substr_replace($return, "s3.amazonaws.com/", strpos($return, "media.mycompany.com"), 0);
答案 0 :(得分:2)
此处的更改是备用存储桶名称格式
几乎。这个库似乎没有你需要的东西,以便你做你想做的事。
对于签名版本2(您正在使用的版本),最简单的解决方法是使用https://bucket.s3.amazonaws.com/path
签名的网址,并将字符串替换为https://s3.amazonaws.com/bucket/path
.¹这是有效的,因为签名在V2中是等效的。它不适用于Signature V4,但您没有使用它。
那,或者您需要重写支持库中的代码以使用路径样式URL的另一个选项来处理这种情况。
“hostbucket”选项似乎假设在桶指向S3端点后命名的CNAME或别名,这不适用于HTTPS。将此选项设置为true实际上会导致库为名为s3.amazonaws.com/media.example.com
的存储桶的URL签名,这就是签名不匹配的原因。
如果您想隐藏URL中的“S3”并使用您自己的SSL证书,可以在S3前面使用CloudFront来完成。使用CloudFront,您可以使用自己的证书,并将其指向任何存储桶,无论存储桶名称是否与原始主机名匹配。但是,CloudFront对签名URL使用非常差异的算法,因此您需要代码来支持它。 CloudFront签名URL的一个优点 - 可能对您有用,也可能没有用 - 是您可以生成一个签名URL,该URL只能使用您在签名策略中包含的特定IP地址。
还可以使用特殊配置的CloudFront传递已签名的S3 URL(将存储桶配置为自定义源,而不是S3源,并将查询字符串转发到源)但这会破坏CloudFront中的所有缓存,因此这有点适得其反......但它会起作用。
¹请注意,在这样重写时必须使用区域端点,除非您的存储桶位于us-east-1(又称美国标准)中,因此对于美国西部的存储区,主机名将为s3-us-west-2.amazonaws.com
例如-2。对于美国标准,s3.amazonaws.com
或s3-external-1.amazonaws.com
可以与https网址一起使用。
答案 1 :(得分:0)
花了几天时间试图为预签名 URL 设置自定义 CNAME/主机,但似乎不可能。
所有论坛都说这无法完成,或者您必须重新编码整个应用程序才能改用 cloudfront。
将我的 DNS 更改为从 MYBUCKET.s3-WEBSITE-eu-west-1.amazonaws.com 指向 MYBUCKET.s3-eu-west-1.amazonaws.com 立即修复了它。
希望这对其他人有帮助。
工作代码:
function get_objectURL($key) {
// Instantiate the client.
$this->s3 = S3Client::factory(array(
'credentials' => array(
'key' => s3_key,
'secret' => s3_secret,
),
'region' => 'eu-west-1',
'version' => 'latest',
'endpoint' => 'https://example.com',
'bucket_endpoint' => true,
'signature_version' => 'v4'
));
$cmd = $this->s3->getCommand('GetObject', [
'Bucket' => s3_bucket,
'Key' => $key
]);
try {
$request = $this->s3->createPresignedRequest($cmd, '+5 minutes');
// Get the actual presigned-url
$presignedUrl = (string)$request->getUri();
return $presignedUrl;
} catch (S3Exception $e) {
return $e->getMessage() . "\n";
}
}