使用GCP Storage getSignedUrl生成签名URL后无法上传文件
我正在实现一套客户端通过服务器生成的签名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

