Php 使用S3签名URL时签名不匹配
我在S3中有一个bucket,它链接到一个CNAME别名。现在让我们假设域是media.mycompany.com。在bucket中是全部设置为private的图像文件。然而,他们在我的网站上公开使用URL签名。签名URL可能如下所示:Php 使用S3签名URL时签名不匹配,php,amazon-s3,Php,Amazon S3,我在S3中有一个bucket,它链接到一个CNAME别名。现在让我们假设域是media.mycompany.com。在bucket中是全部设置为private的图像文件。然而,他们在我的网站上公开使用URL签名。签名URL可能如下所示: $return = $this->s3->getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename, $timestamp, true,
$return = $this->s3->getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename,
$timestamp, true, false);
$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
$timestamp, true, true);
这样做很好。我正在使用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);
$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
$timestamp, true, true);
这将返回本文前面共享的正确签名的URL,一切正常
然而,我现在想要生成HTTPS URL,这就是我遇到问题的地方。简单地将HTTPs添加到当前URL(通过将方法的最后一个参数设置为true)将不起作用,它将生成如下URL:
$return = $this->s3->getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename,
$timestamp, true, false);
$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
$timestamp, true, true);
这显然行不通,因为我的SSL证书(由letsencrypt创建)没有安装在Amazon的域上,据我所知,没有办法这样做
我已经了解了通过SSL访问bucket的另一种URL格式:
这显然适用于某些人,但不适用于我,据我所知,这是因为我的桶名中有一个点(.)字符。我无法更改bucket名称,这将在我的设置中产生重大影响
最后,还有一种格式:
我离这里越来越近了。如果我使用一个不安全的工作URL,并编辑该URL以采用这种格式,它就可以工作。如图所示
现在我想让它以自动化的方式工作,从我前面展示的签名方法开始。我这样称呼它:
$return = $this->s3->getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename,
$timestamp, true, false);
$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
$timestamp, true, true);
这里的更改是可选的bucket name格式,最后一个参数设置为true,表示HTTPs。这将导致如下输出:
$return = $this->s3->getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename,
$timestamp, true, false);
$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
$timestamp, true, true);
如您所见,它的格式与我手工创建的URL相同。但不幸的是,我收到了签名错误:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
我一直在想为什么这些签名不正确。我尝试将signing方法的第4个参数设置为true和false,但没有任何区别
我错过了什么
编辑
根据Michael在下面的回答,在调用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);
这里的更改是可选的bucket名称格式
差不多。这个库似乎没有您想要做的事情
对于签名版本2(您正在使用),最简单的解决方法是使用https://bucket.s3.amazonaws.com/path
只需将字符串替换为https://s3.amazonaws.com/bucket/path
。由于签名在V2中是等效的,所以这是有效的。它对签名V4不起作用,但您没有使用它
或者您需要重写支持库中的代码,以使用路径样式URL的另一个选项来处理这种情况
“hostbucket”选项似乎假定一个以bucket命名的CNAME或别名指向S3端点,这不适用于HTTPS。将此选项设置为true实际上会导致库对名为s3.amazonaws.com/media.example.com
的bucket的URL进行签名,这就是签名不匹配的原因
如果您想从URL中隐藏“S3”并使用自己的SSL证书,可以在S3前面使用CloudFront。使用CloudFront,您可以使用自己的证书,并将其指向任何bucket,而不管bucket名称是否与原始主机名匹配。然而,CloudFront对签名URL使用了一种非常不同的算法,所以您需要代码来支持它。CloudFront签名URL的一个优点是,您可以生成一个签名URL,该URL只能从签名策略中包含的特定IP地址工作,这可能对您有用,也可能对您不有用
也可以通过CloudFront的特殊配置来传递签名的S3URL(将bucket配置为自定义源,而不是S3源,并将查询字符串转发到源),但这会破坏CloudFront中的所有缓存,因此有点事与愿违。。。但这是可行的
⑨请注意,当您这样重写时,必须使用区域端点,除非您的存储桶位于us-east-1(又称美国标准)中,例如,对于us-west-2中的存储桶,主机名应为
s3-us-west-2.amazonaws.com
。对于美国标准,无论是s3.amazonaws.com
还是s3-external-1.amazonaws.com
都可以与https URL一起使用。花了好几天的时间绕圈尝试为预先指定的URL设置自定义CNAME/host,这似乎是不可能的
所有论坛都说这是不可能的,或者你必须重新编码你的整个应用程序来使用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";
}
}
你是个英雄。事实上,这个库根本无法处理所需的格式,而您快速重写字符串的想法很有帮助。我对原始问题进行了编辑,以包含被证明有效的快速而肮脏的代码。