You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用GCP Storage getSignedUrl生成签名URL后无法上传文件

Google Cloud Storage签名URL上传WebM文件出现SignatureDoesNotMatch错误

我正在实现一套客户端通过服务器生成的签名URL上传WebM文件的流程,服务器生成签名URL的环节正常,但客户端上传时收到403错误:

上传文件错误:.... SignatureDoesNotMatch .... 我们计算的请求签名与您提供的签名不匹配。请检查您的Google密钥和签名方法。

签名URL生成后直接返回给客户端,未做任何修改,想请教可能的原因。以下是相关代码和配置:

服务器端代码(Node.js)

// Server Side / node.js / server.js / localhost:4000

import {Storage} from '@google-cloud/storage';

...

const storage = new Storage({
  projectId: 'my_project_id',
  credentials: {
    client_email: 'my_client_email',
    private_key: 'my_private_key',
  }
});

...

const expires = Date.now() + 60 * 60 * 1000; // 1小时

const options = {
  version: 'v4',
  action: 'write',
  expires,
  contentType: 'video/webm',
};
    
// 获取用于上传文件的v4签名URL
const signedUrlData = await storage
  .bucket('my_bucket')
  .file('my_video.webm')
  .getSignedUrl(options);

const [url] = signedUrlData;

return url; // 从@google-cloud/storage正确获取到URL

...

客户端代码(React.js)

// Client Side / react.js / upload.jsx / localhost:3000

...

// 调用服务器API生成签名URL,赋值给uploadDataUrl
const {data: result} = await axios({
  method: 'PUT',
  url: uploadDataUrl,
  headers: {
    'Content-Type': 'video/webm',
  },
  data: videoUrl, // Blob的URL示例: blob:http://localhost:3000/b04758435433-h637-892l-0165-bcuis7893mcd
});

...

错误信息

...

// 上传文件时的403错误:
<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>

...

CORS配置(cors.json)

[
  {
    "origin": [
      "http://localhost:3000",
      "http://localhost:4000"
    ],
    "responseHeader": [
      "Content-Type",
      "Access-Control-Allow-Origin"
    ],
    "method": [
      "PUT",
      "POST",
      "GET",
      "DELETE",
      "OPTIONS"
    ],
    "maxAgeSeconds": 3600
  }
]

问题原因及解决方法

1. 客户端上传数据类型错误

你客户端代码里data传的是Blob的URL,但GCS签名URL要求直接上传二进制文件数据,而非Blob URL。Axios无法解析Blob URL作为请求体,导致实际发送的内容和签名计算时的预期完全不符,触发签名不匹配错误。

解决方法:先将Blob URL转换为Blob对象,再作为请求体上传:

// 先获取Blob对象
const response = await fetch(videoUrl);
const blob = await response.blob();

// 发起上传请求
const {data: result} = await axios({
  method: 'PUT',
  url: uploadDataUrl,
  headers: {
    'Content-Type': 'video/webm',
  },
  data: blob,
});

2. 签名URL过期时间格式不规范

你用Date.now()生成过期时间,它返回的是毫秒级时间戳,但GCS v4签名URL要求过期时间为秒级时间戳或Date对象。虽然@google-cloud/storage有自动转换逻辑,但转换异常时会导致签名计算错误。

解决方法:改用Date对象或秒级时间戳:

// 方式1:使用Date对象
const expires = new Date(Date.now() + 60 * 60 * 1000); // 1小时后

// 方式2:转换为秒级时间戳
const expires = Math.floor(Date.now() / 1000) + 60 * 60; // 1小时后

3. 请求头与签名配置不匹配

签名URL生成时指定了contentType: 'video/webm',客户端上传时必须严格匹配该值,不能有多余空格、大小写错误(比如写成Video/WebM)。只要实际发送的Content-Type和签名时的配置不一致,就会触发签名不匹配。

验证方式:打开浏览器开发者工具,检查请求的Content-Type是否完全等于video/webm。

4. 文件名重复导致的签名冲突(可选排查)

如果多次请求生成了同一个文件名的签名URL,旧URL会因签名规则失效无法使用。如果客户端误用了旧URL,也会触发403错误。建议给每个上传的文件添加唯一标识(比如时间戳),确保文件名唯一。


内容的提问来源于stack exchange,提问作者Sean Vazquez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:25:39