【问题标题】:Signature does not match using Signed Url with GCS使用带有 GCS 的 Signed Url 的签名不匹配
【发布时间】:2017-06-20 00:56:30
【问题描述】:

我打算让我的应用程序的用户将文件直接上传到 GCS,为此我使用带有签名 URL 的 PUT request。我在 python 中的代码如下所示:

def get(self):
    base_url = 'https://storage.googleapis.com/' + self.get_bucket_name()
    expiration = utils.unix_time_secs(datetime.now() + timedelta(hours=1))

    string_to_sign = (  'PUT' + "\n" +
                        '' + "\n" +
                        '' + "\n" +
                        str(expiration) + "\n" +
                        '' + "\n" +
                        'my-bucket.appspot.com')

    signed_string = app_identity.sign_blob(string_to_sign)[1]
    signature = base64.b64encode(signed_string)
    signature = urllib.quote(signature, safe='')
    google_access_id = app_identity.get_service_account_name()

    params = '?GoogleAccessId={0}&Expires={1}&Signature={2}'.format(
                        google_access_id, expiration, signature)


    self.template_values['base_url'] = base_url
    self.template_values['params'] = params
    self.render('testA.html')

在客户端,我使用 XMLHttpRequest 将文件发送到 GCS:

function gCloud (base_url, params, callback) {
    var xhr = new XMLHttpRequest();
    var file = document.querySelector('#fotos').files[0]

    //Here goes an event listener (callback)

    url = base_url + '/' + file.name + params
    xhr.open("PUT", url, true);
    xhr.setRequestHeader("Content-Type", file.type)
    xhr.send(file)
}

问题是,当我发送此请求时,我收到一个 403 禁止错误,表示计算的签名与我提供的签名不匹配。这可能是一个愚蠢的错误,但我无法让它工作。可能是什么问题?

编辑:

我尝试使用 google-cloud-python,并尝试了这段代码(有效):

def get(self):
    bucket = storage.Client().get_bucket(self.get_bucket_name())
    blob = bucket.blob('Yosemite.jpg')

    expiration = utils.unix_time_secs(datetime.now() + timedelta(hours=1))
    base_url = blob.generate_signed_url(expiration, "PUT", 'image/jpeg')
    self.template_values['base_url'] = base_url
    self.render('testA.html')

现在的问题是,由于 'generate_signed_url' 是 Blob 类的一个方法,它已经假定我知道创建 url 的对象的唯一路径,例如 blob = bucket.blob('Yosemite.jpg'),我没有。如果用户想上传 Space.jpg 怎么办? Yosemite.jpg 不会创建 my-bucket/Space.jpg,而是会被覆盖,所以我将拥有 Yosemite.jpg,如果我打开图像,它实际上是 Space.jpg。我该如何解决这个问题?

另外,如果我在 'generate_signed_url' 函数中不包含 Content-Type='image-jpeg',签名将不匹配。如果用户上传的是 png 怎么办?

【问题讨论】:

    标签: python google-app-engine google-cloud-storage


    【解决方案1】:

    URL 签名代码很棘手,而且出了名的难以调试。幸运的是,Google 的 google-cloud 库有一个“generate_signed_url”函数可以为您解决这个问题。我强烈建议您使用它而不是自己重写它。 Here's the documentation.

    即使您不想使用它,也可能需要查看implementation of the signing logic in Python

    现在,如果您想自己调试,检查错误消息非常有用。它将包括服务器检查签名的字符串的完整副本。打印您的“string_to_sign”并查看它是否与从服务器返回的值匹配。如果没有,那是你的问题。如果是,请继续进行实际签名。

    查看您的代码,我猜问题可能是您没有对您的 google_access_id 进行 URL 转义,但我不确定。

    【讨论】:

    • 感谢您的回答。我尝试转义 google_access_id,但这并没有解决它。我尝试使用 google-cloud-python 库并相应地编辑了问题,但据我了解,该函数假定我在将 url 传递给用户之前知道文件名称或其内容类型等内容。我该怎么办?
    • 哦。您正在尝试在客户端编辑 URL 以附加对象名称?那是行不通的。签名 URL 的全部意义在于防止客户端编辑请求。如果您希望用户能够指定对象名称,还有另一种选择:签名的 POST 请求策略文档。见cloud.google.com/storage/docs/xml-api/…
    • 我将在这种情况下使用 POST,其他情况我会保留 PUT。非常感谢您的回答!
    【解决方案2】:

    您似乎缺少规范资源的对象名部分。这是必需的,无法创建允许用户选择文件名的 PUT 签名 URL,您必须在签名 URL 时指定它。

    还请注意,SignatureDoesNotMatch 错误的响应正文应包含一些带有 stringToSign 的 XML——也就是服务器希望您签名的字符串。您可以将其与您实际签名的字符串进行比较,以确定不匹配的来源。

    【讨论】:

    • 你是对的,这是问题的一部分。另一件事是 Content-Type,我必须指定它才能使其工作,即使它应该是可选的。无论如何,我现在正在使用客户端库,它负责处理它。非常感谢您的回答。
    • content-type 是可选的,但 PUT 请求必须与签名匹配。如果您未在签名中指定内容类型,则 PUT 请求也不得包含内容类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-01
    • 1970-01-01
    • 2019-08-17
    • 2018-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多