1

我们在 S3 上的自定义域上托管了大量视频/音频/媒体,并创建了一组函数来对 URL 进行签名并允许它们既可流式传输又可下载。问题是签名的 URL 当然永远不会起作用。错误是:

我们计算的请求签名与您提供的签名不匹配。检查您的密钥和签名方法。

当然,如果我们将此页面返回的字节码输入到Amazon S3 签名测试器并从那里获取字节码,它就可以正常工作。即使从我们的函数中签名的字符串以及签名测试器中解码的字节码相同,它也永远不会起作用。

它通过一小段 PHP 代码调用:

$headers = createS3parameters($expiry, $file_type);
$request = preg_replace("/^.*?:\/\/.*\//", "/", $bucketurl);
$signature = signRequest($request, $expiry, $s3secret, $headers, "GET", $file_type);
$signed_request = "$bucketurl?AWSAccessKeyId=$s3key&Expires=$expiry&$headers&Signature=$signature";

这是实际签署它的功能。

function signRequest($request, $expiration, $s3secret, $headers = '', $type = 'GET', $content_type = 'default')
{
    if ($expiration == 0 || $expiration == null)
    {
        $expiration = time() + 315576000; // 10 years (never)
    }

    if (strcmp($content_type, 'default') == 0)
    {
        $content_type = "";
    }

    // S3 tester spits out this format
    /*$string = "$type\n".
              "\n\n".
              "$expiration\n".
              "/mybucket$request?$headers";*/

    $string = "$type\n".
              "\n".
              "$content_type\n".
              "$expiration\n".
              "$headers\n".
              "$request";


    // must be in UTF8 format
    $string = utf8_encode(trim($string));
    // encode to binary hash using sha1. require S3 bucket secret key
    $hash = hash_hmac("sha1",$string, $s3secret,false);
    // sha1 hash must be base64 encoded
    $hash = base64_encode($hash);
    // base64 encoded sha1 hash must be urlencoded
    $signature = rawurlencode($hash);

    return $signature;
}

然后创建一个 URL,例如:

http://mybucket.s3.amazonaws.com/My_Boring_Video.wmv?AWSAccessKeyId=AKIAIEXAMPLE6GA3WYQ&Expires=1344160808&response-content-type=application/force-download&response-expires=1344160808&Signature=OTIxOTI0YjNjMTA1NjMyNmJjYTk0MGE2YWJkMmI5OWQ3MGM2ZGY0MQ%3D%3D

不幸的是,这不起作用。这里有一个明显的问题,我一直盯着太久无法正确弄清楚吗?

4

1 回答 1

2

更新:规格是规格,但只有在符合实际情况时才有帮助。

亚马逊的 S3 规范说签名应该形成如下:

Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) );

StringToSign = HTTP-VERB + "\n" +
    Content-MD5 + "\n" +
    Content-Type + "\n" +
    Expires + "\n" +
    CanonicalizedAmzHeaders +
    CanonicalizedResource;    

但是,所需的实际请求如下所示:

 StringToSign = HTTP-VERB + "\n" +
     "\n" + 
     "\n" +
     Expires + "\n" +
     Bucket + CanonicalizedResource + "?" + CanonicalizedAmzHeaders;

奇怪的是,在 PHP 中你似乎也不能这样做:

$string = "$type\n".
          "\n".
          "\n".
          "$expiration\n".
          "/$bucket$request?$headers";

它更改了签名并最终被拒绝,因此必须全部在一行上。我还没有检查这是否是我们特定版本的 PHP 中的错误或只是一般的 PHP 错误(或者可能是预期的功能??!)。即使您使用的是可用的虚 URL,例如 mybucket.s3.amazonaws.com 或 mybucket.mydomain.com,您也必须包含存储桶的名称。该文档没有指定您可以做什么或不能做什么,我假设由于我们使用的是基于 S3 的虚 URL,它 (S3) 会获取域名并将其转换为存储桶。

我最终将我的功能更改为以下内容:

function signRequest($bucket, $request, $expiration, $s3secret, $headers = '', $type = 'GET', $content_type = 'default')
{
    if ($expiration == 0 || $expiration == null)
    {
        $expiration = time() + 315576000; // 10 years (never)
    }

    if (strcmp($content_type, 'default') == 0)
    {
        $content_type = "";
    }

    $headers = trim($headers, '&');

    // This is the spec:
    /*$string = "$type\n".
        "\n".
        "$content_type\n".
        "$expiration\n".
        "$headers\n".
        "$bucket$request";*/
    // but it will only work as this
    $string = "$type\n\n\n$expiration\n/$bucket$request?$headers";

    // this could be a single line of code but left otherwise for readability
    // must be in UTF8 format
    $string = utf8_encode(trim($string));
    // encode to binary hash using sha1. require S3 bucket secret key
    $hash = hash_hmac("sha1",$string, $s3secret,true);
    // sha1 hash must be base64 encoded
    $hash = base64_encode($hash);
    // base64 encoded sha1 hash must be urlencoded
    $signature = urlencode($hash);

    return $signature;
}

希望其他人也觉得这很有用。

更新(20180109):添加调用它的函数(也就是让我们把它变得非常简单)。

它有助于理解传递给 signRequest() 函数的内容。

    private function genS3QueryString($bucketurl)
    {
            $file_type = 'application/force-download';
            $expiry = '1831014000'; //Sun, Jan 09 2028 0700 UTC

            $headers = '&response-content-type='.$file_type.'&response-expires='.$expiry;

            $bucket = preg_replace("/^.*?:\/\/(.*)\.s3\.amazonaws\.com\/.*/", "$1", $bucketurl);
            $request = preg_replace("/^.*?:\/\/.*\//", "/", $bucketurl);
            $signature = $this->signRequest($bucket, $request, $expiry, S3_SECRET_KEY, $headers, 'GET', $file_type);

            $signed_request = '?AWSAccessKeyId='.S3_KEY.'&Expires='.$expiry.$headers.'&Signature='.$signature;

            return $signed_request;
    }

但是您会注意到,第一个函数仅生成附加到 AWS 的 GET 请求所需的加密签名部分。根据文档(请参阅上面的链接),请求需要更多,正如上面的第二个函数形成的那样。

如果您愿意,我认为到期时间可以是滚动到期时间,但在未来应该足够长,以使资产在实际到期时间之前很久就过时并被替换。选择了任意时间以充分超过当前版本的网站。

第二个功能只需要受保护资产的存储桶 url。可以将所需的响应类型添加到调用中或简单地更改,以便将资产(在这种情况下只是不可显示的)文档作为不同的类型下载。

然后必须将返回的signed_request 附加回bucketurl,以便工作URI 向受保护的S3 资产请求

于 2012-08-02T15:44:54.120 回答