向Google Cloud签名URL发送POST请求返回403 Forbidden问题排查
问题分析:GCS签名URL携带
x-goog-custom-time时请求返回403错误 我使用Google Cloud Storage API生成签名URL的代码如下:
url, err := s.client.Bucket(s.bucketName).SignedURL(filename, &storage.SignedURLOptions{ GoogleAccessID: jwtConf.Email, PrivateKey: jwtConf.PrivateKey, Expires: s.ExpirationTime, ContentType: s.ContentType, Scheme: storage.SigningSchemeV4, Method: "POST", Headers: []string{"x-goog-custom-time:2022-12-12T23:20:50Z", "x-goog-resumable:start"}, })
生成的签名URL包含预期的签名请求头:
https://storage.googleapis.com/####/test_file?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=####%40####%2F20221030%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20221030T122044Z&X-Goog-Expires=3578&X-Goog-Signature=####&X-Goog-SignedHeaders=host%3Bx-goog-custom-time%3Bx-goog-resumable
但发送POST请求启动可恢复上传时,收到403 Forbidden错误,请求命令如下:
curl --location --request POST '<my_signed_url>' \ --header 'content-length: 0' \ --header 'content-type: application/json' \ --header 'x-goog-custom-time: 2022-12-12T23:20:50Z' \ --header 'x-goog-resumable: start'
错误响应内容:
<?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>GOOG4-RSA-SHA256 20221030T122044Z 20221030/auto/storage/goog4_request ea35f34d2dc65e9345e5a40a4f76ecddbd306cc25a9e19fa97e056cdd2f0363a</StringToSign><CanonicalRequest>POST /####/test_file X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=####;X-Goog-Date=20221030T122044Z&X-Goog-Expires=3578&X-Goog-SignedHeaders=host%3Bx-goog-custom-time%3Bx-goog-resumable host:storage.googleapis.com x-goog-custom-time:2022-12-12T23:20:50Z x-goog-resumable:start host;x-goog-custom-time;x-goog-resumable UNSIGNED-PAYLOAD</CanonicalRequest></Error>
关键现象:从签名URL生成逻辑和请求中移除x-goog-custom-time请求头后,POST请求成功执行。
失败原因解析
问题核心在于x-goog-custom-time的属性及GCS V4签名的规则:
x-goog-custom-time属于对象元数据头,而非普通请求头。你在SignedURLOptions.Headers中配置它时,签名过程会将其当作请求头进行签名,但在可恢复上传的初始化POST请求中,GCS服务端会自动把这个头解析为对象元数据,而非请求头,导致签名验证环节的请求头匹配逻辑出错。- 可恢复上传的初始化请求不支持将
x-goog-custom-time作为请求头传递,若要设置该元数据,需通过SignedURLOptions的Metadata字段配置,而非Headers。
修正后的代码示例
url, err := s.client.Bucket(s.bucketName).SignedURL(filename, &storage.SignedURLOptions{ GoogleAccessID: jwtConf.Email, PrivateKey: jwtConf.PrivateKey, Expires: s.ExpirationTime, ContentType: s.ContentType, Scheme: storage.SigningSchemeV4, Method: "POST", Headers: []string{"x-goog-resumable:start"}, Metadata: map[string]string{"x-goog-custom-time": "2022-12-12T23:20:50Z"}, })
修正后,签名过程会正确处理元数据,请求时也无需在curl中添加x-goog-custom-time头,服务端即可完成签名验证并正确设置对象的自定义时间。
内容的提问来源于stack exchange,提问作者Rotem Linik
相关产品推荐
相关产品推荐

