【问题标题】:Error with signed writable URL: The request signature we calculated does not match the signature you provided签名可写 URL 出错:我们计算的请求签名与您提供的签名不匹配
【发布时间】:2018-11-22 01:24:19
【问题描述】:

我正在将一个虚拟文件上传到 Google Cloud Storage,然后签署一个读/写 URL,然后再将其传递给另一个单独托管的服务。不幸的是,我收到了 403 响应,其中包含以下消息:

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

我用来创建虚拟对象并签署 URL 的代码:

const string BASE64_JSON_CREDS = "UklQIFN0YW4gTGVl"; // credentials of service account with "Storage Admin" role (entire json file as received from Google's Console)
const string BUCKET = "testbucket";
const string FILENAME = "test.jpg";
byte[] imageBytes = File.ReadAllBytes(@"test.jpg");

GoogleCredential credentials = null;
using (var stream = new MemoryStream(Convert.FromBase64String(BASE64_JSON_CREDS)))
{
    credentials = GoogleCredential.FromStream(stream);
}

StorageClient storageClient = StorageClient.Create(credentials);
var bucket = await storageClient.GetBucketAsync(BUCKET);
await storageClient.UploadObjectAsync(bucket.Name, FILENAME, null, new MemoryStream());

var scopedCreds = credentials.CreateScoped("https://www.googleapis.com/auth/devstorage.read_write").UnderlyingCredential as ServiceAccountCredential;
var urlSigner = UrlSigner.FromServiceAccountCredential(scopedCreds);
var url = urlSigner.Sign(bucket.Name, FILENAME, TimeSpan.FromHours(100));

为了这个问题我写了一些测试代码(我也试过HttpWebRequest):

var handler = new HttpClientHandler()
{
//    Proxy = new WebProxy("localhost", 8888)
};
var client = new HttpClient(handler);
var content = new ByteArrayContent(imageBytes, 0, imageBytes.Length);
content.Headers.Remove("Content-Type");
var response = await client.PutAsync(url, content);
if (response.IsSuccessStatusCode)
{
    Console.WriteLine("yay");
}
else
{
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}
Console.ReadKey();

通过 Fiddler 代理时,请求如下所示:

PUT https://storage.googleapis.com/testbucket/test.jpg?GoogleAccessId=testbucket@testproject.iam.gserviceaccount.com&Expires=1543209340&Signature=j1cagZ9MHZQAIeYrzbm95MWsIdFMvX1Em13il%2F2nEB1qx9xGB6%2BUzt6vo2OVuRp2TlW1G1TtyX32lxbH%2Fb51dr49eFBcSSm9H8rSXtuEXci02dY%2Fe%2FV0n4kpVwDjpiq4QVSMM%2BaCEdrUtPxT69BSoDuRqh6UHkeOL6VqLgcHGKQcXraZCrEaCXCJfNBwBlPcoXzOD708Nasl99ahxGwcPY6s1FXLCiAiP0VDJSRrPqbE8LHyRLLTgCk9r2H4pEW%2BpGpjEWj3DVpDC334%2BQQFttzDNuZQnUMtZi%2BGz5rqQbU5hBLgthb%2B13884uL4eUalnoSuRfR9JPKIJP7xk3%2FH4g%3D%3D HTTP/1.1
Content-Length: 21925
Host: storage.googleapis.com

{IMAGE_CONTENT}

响应是:

HTTP/1.1 403 Forbidden
X-GUploader-UploadID: AEnB2UoklkZmIP8odWSx14Y0ZDgxjM8ZM94SCfNgAONG1giFTd9cncH8bAMK3s7I7v2DC1NwVirOrNbTjnBzdS2o1tOGX2pLBg
Content-Type: application/xml; charset=UTF-8
Content-Length: 314
Date: Thu, 22 Nov 2018 01:15:39 GMT
Server: UploadServer
Alt-Svc: quic=":443"; ma=2592000; v="44,43,39,35"

<?xml version='1.0' encoding='UTF-8'?><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message><StringToSign>PUT


1543209340
/testbucket/test.jpg</StringToSign></Error>

查看谷歌的UrlSigner code,上面列出的行是:

var signatureLines = new List<string>
{
    requestMethod.ToString(),
    contentMD5,
    contentType,
    expiryUnixSeconds
};

我在this question 中发现了Content-Type 标头确实 需要设置的建议,因此我进行了以下更改:

// New signing code
var headers = new Dictionary<string, IEnumerable<string>>() { { "Content-Type", new string[] { "image/jpeg" } } };
var url = urlSigner.Sign(bucket.Name, FILENAME, TimeSpan.FromHours(100), requestHeaders: headers);

// New put code (I removed the line removing Content-Type)
var content = new ByteArrayContent(imageBytes, 0, imageBytes.Length);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");

但这并没有解决问题。新的“StringToSign”值反映了变化:

PUT

image/jpeg
1543214247
/teams-storage-test-bucket/test.jpg

所以它应该(在我看来)检查的标题与发送的内容是正确的。生成的 URL 适用于 GET(我可以下载空文件),但不适用于 PUT。有解决办法吗?

【问题讨论】:

  • 对于您正在加载凭据的行,您是加载整个 json 文件 base64 编码还是只是其中的一部分:const string BASE64_JSON_CREDS=。要正确创建凭据,您需要包含在凭据 json 文件中的 client_emailprivate_key
  • @John 从 Google 收到的整个文件。相同的凭据允许我通过ServiceClient 上传文件、下载文件和删除文件,还允许我创建可恢复的上传(通过 REST API,因为我需要一个 Angular Web 前端来执行上传)。跨度>
  • 你需要在你的tourlSigner.Sign(bucket.Name, FILENAME, TimeSpan.FromHours(100), HttpMethod.Put, contentHeaders: new Dictionary&lt;string, IEnumerable&lt;string&gt;&gt; { { "Content-Type", new[] { "image/jpeg" } } });urlSigner.Sign(bucket.Name, FILENAME, TimeSpan.FromHours(100), HttpMethod.Put, contentHeaders: new Dictionary&lt;string, IEnumerable&lt;string&gt;&gt; { { "Content-Type", new[] { "image/jpeg" } } });中指定HTTP方法和头
  • @John 您能否将其添加为答案,以便我接受我的屈辱并给予您应得的支持? :-) 我天真地假设(没有足够仔细地查看重载)指定范围等同于为 S3 指定动词等。

标签: c# google-cloud-platform google-cloud-storage pre-signed-url


【解决方案1】:

在您对 Sign() 的调用中包含 HTTP Method 和 Content-Type 标头:

urlSigner.Sign(bucket.Name, FILENAME, TimeSpan.FromHours(100), HttpMethod.Put, contentHeaders: new Dictionary<string, IEnumerable<string>> { { "Content-Type", new[] { "image/jpeg" } } });

【讨论】:

  • 我现在感觉自己像个白痴。感谢您的帮助。
【解决方案2】:

通过在上传期间在签名 URL 和 PUT 请求中添加相同的内容类型对我有用

file.getSignedUrl({
    action:"write",
    expires:(Date.now() + expDuration),
    contentType: "text/csv",  <-- add your type 
});

并在上传请求中添加标头

"Content-Type":"text/csv"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 2021-10-15
    • 2014-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多