【问题标题】:Signature mismatch when using S3 signed URLs使用 S3 签名 URL 时的签名不匹配
【发布时间】:2016-05-21 12:39:25
【问题描述】:

我在 S3 中有一个与 CNAME 别名相关联的存储桶。我们现在假设域是 media.mycompany.com。存储桶中有所有设置为私有的图像文件。然而,它们在我的网站上使用 URL 签名公开使用。签名的 URL 可能如下所示:

http://media.mycompany.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&Expires=1466035210&Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D

这可以正常工作。我在 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,这就是我遇到问题的地方。简单地将 HTTPs 添加到当前 URL(通过将方法的最后一个参数设置为 true)是行不通的,它会生成一个如下所示的 URL:

https://media.mycompany.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&Expires=1466035210&Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D

这显然行不通,因为我的 SSL 证书(由letsencrypt 创建)没有安装在亚马逊的域上,据我所知,没有办法这样做。

我了解了一种通过 SSL 访问存储桶的替代 URL 格式:

https://media.mycompany.com.s3.amazonaws.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&Expires=1466035210&Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D

这显然适用于某些人,但不适用于我,据我所知,这是由于我的存储桶名称中有一个点 (.) 字符。我无法更改存储桶名称,这会对我的设置产生重大影响。

终于有了这样的格式:

https://s3.amazonaws.com/media.mycompany.com/images/2428/39000_small.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&Expires=1466035210&Signature=6p3W6GHQtddJNnCoUXaNl970x9s%3D

在这里,我已经非常接近了。如果我采用一个有效的非安全 URL,并编辑该 URL 以采用这种格式,它就可以工作。显示图像。

现在我想让它通过我之前展示的签名方法以自动化方式工作。我这样称呼它:

$return = $this->s3->getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
                    $timestamp, true, true);

这里的变化是替代的bucket名称格式,最后一个参数设置为true,表示HTTPS。这会导致这样的输出:

https://s3.amazonaws.com/media.mycompany.com/images/2784/38965_small.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&Expires=1466035210&Signature=Db2ynwWOV852Mn4rpcWA0Q1DrH0%3D

如您所见,它与我手动制作的 URL 具有相同的格式。但不幸的是,我遇到了签名错误:

<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);

【问题讨论】:

    标签: php amazon-s3


    【解决方案1】:

    这里的变化是替代的bucket名称格式

    几乎。这个库似乎没有你想要做的事情所需要的东西。

    对于签名版本 2(您正在使用的版本),您最简单的解决方法是使用 https://bucket.s3.amazonaws.com/path 获取签名 URL,然后将字符串替换为 https://s3.amazonaws.com/bucket/path。¹这是有效的,因为签名是等效的在 V2 中。它不适用于 Signature V4,但您没有使用它。

    那样,或者您需要重写支持库中的代码以使用路径样式 URL 的另一个选项来处理这种情况。

    “hostbucket”选项似乎假定以存储桶命名的 CNAME 或别名指向 S3 端点,这不适用于 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(又名美国标准)中,因此对于 us-west-2 中的存储桶,主机名将是 s3-us-west-2.amazonaws.com , 例如。对于美国标准,s3.amazonaws.coms3-external-1.amazonaws.com 可以与 https URL 一起使用。

    【讨论】:

    • 你是个英雄。事实上,这个库根本无法按原样处理所需的格式,而您对快速字符串重写的想法有所帮助。我编辑了原始问题以包含被证明有效的快速而肮脏的代码。
    【解决方案2】:

    花了几天的时间试图为预签名 URL 设置自定义 CNAME/主机,但这似乎是不可能的。

    所有论坛都说不能这样做,或者您必须重新编码整个应用程序才能使用云端。

    将我的 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";
    
            } 
        }
    

    【讨论】:

      猜你喜欢
      • 2014-09-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 2019-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多