【问题标题】:Google Storage REST PUT With Signed URLs带有签名 URL 的 Google Storage REST PUT
【发布时间】:2024-04-29 16:20:02
【问题描述】:

我正在尝试使用签名 URL 作为身份验证,通过 javascript 将图像的 base64 数据直接上传到 Google 存储,这显然是可以做到的。

根据developers.google.com/storage/docs/reference-methods#putobject,只需设置六个标头即可使其正常工作。同样对于标题“授权”,我试图在这里使用最后一个选项:

developers.google.com/storage/docs/reference-headers#authorization

这是“签名”developers.google.com/storage/docs/authentication#service_accounts

我想要使用 PHP 的唯一目的就是获取签名。这是我一直在努力但没有成功的原因。

PHP & JS 页面/代码

<?php

$theDate   = Date(DATE_RFC822);

function signedURL( $filename, $bucket, $method = 'PUT' ) {
    $signature  = "";
    $duration   = 30;
    $emailID    = "980000000000-ytyertyr@developer.gserviceaccount.com";
    $certs      = array();
    $priv_key   = file_get_contents("9999999999999999999999999999-privatekey.p12");

  if (!openssl_pkcs12_read($priv_key, $certs, 'notasecret')) { echo "Unable to parse the p12 file. OpenSSL error: " . openssl_error_string(); exit(); }

    $expires = time() + $duration;
    $to_sign = ( $method . "\n\n\n" . $expires . "\n" . "/" . $bucket . "/" . $filename ); 

    $RSAPrivateKey = openssl_pkey_get_private($certs["pkey"]);

  if (!openssl_sign( $to_sign, $signature, $RSAPrivateKey, 'sha256' ))
  {
    error_log( 'openssl_sign failed!' );
    $signature = 'failed';
  } else {
    $signature =  urlencode( base64_encode( $signature ) );
  }

  return ( 
    'http://storage.googleapis.com/' . $bucket . '/' . $filename . '?GoogleAccessId=' . $emailID . '&Expires=' . $expires . '&Signature=' . $signature
         );
    openssl_free_key($RSAPrivateKey);
} 
?>
<script>
var base64img  = 'data:image/png;base64,AAABAAIAICA....snip...A';
var xhr        = new XMLHttpRequest();
//PUT test - PUT status "(Canceled)" - OPTION status 200 (OK)
xhr.open("PUT", "<?php echo signedURL('test.png', 'mybucket'); ?>");
//xhr.setRequestHeader("Content-type", "image/png");
xhr.setRequestHeader("x-goog-acl", "public-read"); //try to set public read on file
xhr.setRequestHeader("Content-Length", base64img.length); // Chrome throws error (Refused to set unsafe header "Content-Length" )
xhr.send( base64img );
//GET test.txt temp file - working and returning 200 status (signing must be working ?)
/*
xhr.open("GET", "<?php echo signedURL('test.txt', 'mybucket', 'GET'); ?>");
xhr.send();
*/
//
</script>

Cors xml(似乎很好)- 我只在测试时设置了通配符,并且缓存/最大化时间较短

<?xml version="1.0" ?>
<CorsConfig>
    <Cors>
        <Origins>
            <Origin>*</Origin>
        </Origins>
        <Methods>
            <Method>GET</Method>
            <Method>HEAD</Method>
            <Method>OPTIONS</Method>
            <Method>PUT</Method>
        </Methods>
        <ResponseHeaders>
            <ResponseHeader>accept-encoding</ResponseHeader>
            <ResponseHeader>cache-control</ResponseHeader>
            <ResponseHeader>content-length</ResponseHeader>
            <ResponseHeader>content-type</ResponseHeader>
            <ResponseHeader>expect</ResponseHeader>
            <ResponseHeader>if-modified-since</ResponseHeader>
            <ResponseHeader>origin</ResponseHeader>
            <ResponseHeader>range</ResponseHeader>
            <ResponseHeader>referer</ResponseHeader>
            <ResponseHeader>x-goog-acl</ResponseHeader>
            <ResponseHeader>x-goog-api-version</ResponseHeader>
        </ResponseHeaders>
        <MaxAgeSec>900</MaxAgeSec>
    </Cors>
</CorsConfig>

我已经在文件上测试了 GET 方法,现在返回 200 状态(\n\n - 修复)

更新:

在 Firefox 中查看它确实返回 403,这与 Chrome 不同。

【问题讨论】:

    标签: php javascript upload base64 google-cloud-storage


    【解决方案1】:

    所以以下几行很奇怪,因为将签名 URL 与 OAuth 和 PUT 与 POST 合并:

    # This looks like a PUT to signed URL
    xhr.open("PUT", '<?php echo signedURL('imgfile.png','PUT',30,'mybucketname'); ?>', true);
    # But multipart requires POST
    xhr.setRequestHeader("Content-type", "multipart/form-data; boundary="+boundary);
    # And here's a second form of authorization
    xhr.setRequestHeader("Authorization", "OAuth <?php echo $signature; ?>");
    

    multipart/form-data 上传需要 POST 动词,用于 html 表单:Google Cloud Storage : PUT Object vs POST Object to upload file.?

    只要您在 XMLHttpRequest 中发送自定义标头,我建议您使用 PUTeither OAuth credentials:

    xhr.open("PUT", "https://storage.googleapis.com/mybucketname/imgfile.png");
    xhr.setRequestHeader("Authorization", "OAuth Bearer 1234567abcdefg");
    xhr.setRequestHeader("Content-Length", raw_img_bytes.length);
    xhr.send(raw_img_bytes);
    

    signed url:

    xhr.open("PUT", "https://storage.googleapis.com/mybucketname/imgfile.png?" + 
                    "GoogleAccessId=1234567890123@developer.gserviceaccount.com&" +
                    "Expires=136891473&" +
                    "Signature=BClz9e...WvPcwN%2BmWBPqwg...sQI8IQi1493mw%3D");
    xhr.setRequestHeader("Content-Length", raw_img_bytes.length);
    xhr.send(raw_img_bytres);
    

    【讨论】:

    • 嗨,费塔。谢谢..我知道会有一些问题,因为我真的对这些东西一无所知。我将采用您的 PUT 方法建议并使用签名的 URL。现在看起来更近了一步,因为我实际上看到的是 PUT 调用,而不仅仅是 OPTIONS 调用。但是,PUT 调用返回“SignatureDoesNotMatch”错误。你知道我是否在这里正确地做 openssl 的东西吗?再次感谢。
    • 比较 $to_sign 的值和要在 PUT 响应正文中签名的字符串。它们完全匹配吗?
    • 很好,这不一样我必须添加两个新行 (\n\n) 才能获得匹配。不幸的是,事情仍然没有奏效。在进行 PUT 调用时,在网络下的 Chrome 开发工具中,状态返回为“(已取消)”。在 cors 中,我确实在那里设置了 responseheader 'accept-encoding' 我想知道它是否会接受作为 base64 的数据并且我也不再发送边界行话,这需要吗?很抱歉很痛苦,但这是我在没有 gsutil 或浏览器管理器的情况下上传到 GCS 的最远距离。我曾经尝试过使用 ogg 文件并放弃了。
    • 所以您不再收到 SignatureDoesNotMatch 错误,但 PUT 被取消了?另外,您能否修复问题中的示例代码以匹配您当前的代码?
    • 是的。我还测试了 GET 方法,所以我认为这部分现在可以工作了。 PUT 在 Chrome 中没有响应或预览,但它确实有一个请求负载标头,其中包含 base64 数据。谢谢。
    【解决方案2】:

    我猜你的 Content-Type 是已知的(例如 Content-Type:video/mp4)?尝试上传扩展名未知的文件。对我来说,PUT 在这种情况下工作,而不是在 Content-Type 不为空时...... 我不明白为什么...

    【讨论】:

    • 事实证明,cors 阻止我看到返回的 xml 错误。 cors 的配置很好,但是在同一个域上设置测试时,我能够看到 xml 错误回来了。您对内容类型是正确的。使用 PUT(与 GET 不同)即使您没有设置内容类型标头,您仍然必须将内容类型放在“to sign”值中。似乎任何额外的标题也是如此。我现在的问题是被 PUT 的图像返回“图像由于错误而无法显示”。似乎每一步都是一场战斗。
    • 你说得对,谢谢!如果其他人阅读此内容,也许您可​​以编辑您的第一篇文章以修改您的 php 部分...我没有图像上的错误返回,而我的配置非常相似...祝您的代码好运
    最近更新 更多