我正在尝试创建一个签名 URL,用于将文件直接上传到 Google Cloud Storage (GCS)。我使用 POST 使用 this Github example 进行了这项工作,它使用了一项政策。根据最佳实践,我正在重构以使用 PUT 并得到一个 SignatureDoesNotMatch
错误:
<?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
123456789
/mybucket/mycat.jpg</StringToSign></Error>
根据 creating a signed URL with a program 和 GCP example Python code 上的文档,我正在执行此过程:
- 构建我的签名字符串
- 签字
- 对其进行base64编码
- url 对结果进行编码(尽管 python 示例不这样做...
由于这是在 Google App Engine (GAE) 应用程序上运行,因此我不需要为我的服务帐户用户获取 JSON key 文件,而是使用 App Identity Services 对其进行签名。这是我在 Flask 项目中的代码:
google_access_id = app_identity.get_service_account_name()
expires = arrow.utcnow().replace(minutes=+10).replace(microseconds=0).timestamp
resource = '/mybucket/mycat.jpg'
args = self.get_parser.parse_args()
signature_string = 'PUT\n'
# take MD5 of file being uploaded and its content type, if provided
content_md5 = args.get('md5') or ''
content_type = args.get('contenttype') or ''
signature_string = ('PUT\n'
'{md5}\n'
'{content_type}\n'
'{expires}\n'
'{resource}\n').format(
md5=content_md5,
content_type=content_type,
expires=expires,
resource=resource)
log.debug('signature string:\n{}'.format(signature_string))
_, signature_bytes = app_identity.sign_blob(signature_string)
signature = base64.b64encode(signature_bytes)
# URL encode signature
signature = urllib.quote(signature)
media_url = 'https://storage.googleapis.com{}'.format(resource)
return dict(GoogleAccessId=google_access_id,
Expires=expires,
Signature=signature,
bucket='mybucket',
media_url='{}?GoogleAccessId={}&Expires={}&Signature={}'.format(media_url, google_access_id, expires, signature))
log.debug
语句打印一个签名文件,它与上面 GCS XML 错误响应中的签名完全匹配。如果它们匹配,那为什么我不能上传?
使用 gsutil
,我可以使用相同的 GAE 服务帐户创建签名 URL,并且它在 Postman 中运行良好。我看到 gsutil
对签名进行 URL 编码,但是在创建我自己的签名 URL 时,这两种方式似乎都不重要:GCS 收到我的 PUT 请求并提示签名不匹配,即使尽管它显示的签名与我记录的调试消息匹配。我也尝试过在原始签名字符串中使用和不使用尾随\n。
编辑: POST
示例我在 Base64 encodes the Policy before it sings 之后对其进行了签名,并再次。我在 PUT 签名创建中尝试了这种方法,但没有任何区别
最佳答案
答案与在 SO 和其他地方找到的其他答案非常接近,指出需要使用 Content-Type
header 。这部分是正确的,但被我的主要问题所掩盖:我依赖于具有“编辑器”权限的默认 GAE 服务帐户,我认为它可以读取和写入 GCS。我从该帐户创建了一个 key 文件,并将其与 gsutil
一起使用,然后给了我这个线索:
myDefaultGAEserviceaccount@appspot.gserviceaccount.com does not have permissions on gs://mybucket/cat.jpg, using this link will likely result in a 403 error until at least READ permissions are granted
当我尝试将文件放在那里时,我从 GCS 收到错误是正确的,但这不是 404 错误,而是问题中显示的 SignatureDoesNotMatch
错误。
解决方案分为两部分:
- 从“存储”权限集中为默认 GAE 服务帐户授予更多权限:
- 存储对象创建者
- 存储对象查看器
- 确保在将文件放入 GCS 时使用
Content-Type
header ,因为即使我没有在要签名的签名字符串中指定它,它也会默认为text/plain
并在标题中要求。
最后一个观察:即使在我向我的帐户添加了正确的权限之后,我仍然收到 gsutil 的警告,直到我从 IAM 控制台创建一个新的 JSON key 文件。此外,即使在 IAM 控制台上设置了权限,使用 gsutil 和旧 key 文件创建的签名 URL 也无法正常工作(SignatureDoesNotMatch
错误)。在 GAE 上运行的 Python 代码在没有任何更新的情况下运行良好——它只需要在 IAM 中设置的权限,然后匹配 Content-Headers。
关于python - 使用 Python 为 GCS PUT 请求在 GAE 上签名的 URL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47211584/