2012-08-01 130 views
1

我们在S3上的自定义域中托管了大量视频/ audo /媒体,并创建了一组函数以签署这些URL并允许它们同时流式传输下载。问题是签名的URL当然不起作用。错误是:Amazon S3签名的URL以更改内容类型

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

当然,如果我们把从这个页面返回的字节码输入到Amazon S3 Signature Tester并从那里获取字节码,它就可以工作。即使从我们的函数签名的字符串以及签名测试程序中解码的字节码都是相同的,它也不会起作用。

它通过的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 

不幸的是不起作用。这里有一个显而易见的问题,我一直在盯着太久而无法正确理解?

+0

您使用什么播放器播放视频?通常玩家正在追加一些变量,这是问题 – 2012-08-01 17:41:49

+0

这个问题与流式传输vs下载无关。当你通过http://mybuckets.s3.amazonaws.com/My_Boring_Video.wmv访问视频时,它工作得很好。问题在于签名的URL经常被AmazonS3拒绝。 – dcmbrown 2012-08-01 19:48:01

回答

2

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

Amazon的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,您也必须包含您的存储桶名称。 documentation没有指定你可以做什么或不可以做什么,我做了一个假设,因为我们使用的是基于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; 
} 

希望别人认为这也是有用的。

UPDATE(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资产请求。

+0

它没有为我工作:http://stackoverflow.com/questions/12079366/wrong-sha1-from-php-cf-according-to-amazon-s3 – SamGoody 2012-08-22 18:50:58

+0

你能告诉我你在$ request中传递了什么,并且您创建的函数的$ bucket参数? – 2018-01-06 12:38:53

+0

您的存储桶是您唯一的存储桶名称。因此,如果您通过网址https://our-site-files.s3.amazonaws.com/assets/pdfs/report.pdf访问存储区项目,您的存储区就是:我们的网站文件。同样,您的请求将是:/assets/pdfs/report.pdf – dcmbrown 2018-01-09 17:32:55