通过预签名URL上传至GCS失败,疑因Header大小写不匹配
问题描述
生成GCS预签名URL的代码
// args包含文件名和类型 // config包含存储桶等信息 const uploadOptions = { destination: args.name, }; console.log(`Content-Type: ${args.fileType}`) const signingOptions = { version: "v4", action: "write", expires: Date.now() + 15*60*1000, contentType: args.fileType, }; const response = Promise.await(myStorage.bucket(config.bucketName).file(args.name, uploadOptions).getSignedUrl(signingOptions)); return response;
使用Fetch上传的代码
// writeUrl为上述代码生成的URL fetch(writeUrl, {method: "PUT", origin: "https:localhost:3000", headers: {"content-type": fileElement.files[0].type}, body: fileElement.files[0]}) .then((response) => { // 执行操作 }) .catch((error) => { console.log(error); })
服务器返回错误
<Error> <Code>MalformedSecurityHeader</Code> <Message>Invalid argument.</Message> <Details>Your request has a malformed header. Header was included in signedheaders, but not in the request.</Details> <ParameterName>content-type</ParameterName> </Error>
补充信息:Chrome网络面板的请求头
Content-Type: image/png Referer: http://localhost:3000/ Sec-Ch-Ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "macOS" User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
上传失败,错误提示未包含content-type请求头,但我已添加。Chrome网络面板显示请求头为Content-Type(首字母大写),怀疑是大小写不匹配导致问题。无法阻止fetch将content-type修改为Content-Type,请问问题根源是什么?该如何解决?
问题根源与解决方法
问题根源
- 签名与请求头的大小写校验冲突:GCS V4签名会将你指定的
contentType转换为小写的content-type加入签名校验列表,但浏览器发送请求时会自动将请求头标准化为驼峰式的Content-Type。虽然HTTP头本身不区分大小写,但GCS的签名校验逻辑会严格对比签名中声明的头名与实际请求的头名,最终判定content-type头未被发送。 - 无效的Origin头设置:你在fetch中手动设置的
origin: "https:localhost:3000"格式错误(缺少斜杠,应为https://localhost:3000),且手动设置Origin头可能干扰浏览器的正常请求处理,进一步加剧校验失败的概率。
解决方法
方案1:移除签名中的Content-Type校验(最简单)
如果不需要强制校验上传文件的Content-Type,直接删除签名配置中的contentType选项,这样GCS不会将content-type加入签名校验列表,上传时的Content-Type头不会触发校验:
const signingOptions = { version: "v4", action: "write", expires: Date.now() + 15*60*1000, // 移除contentType选项 };
方案2:强制校验Content-Type的正确配置
若必须校验Content-Type,需确保签名与请求的头信息完全匹配:
- 修正签名配置:明确指定
signedHeaders为content-type,确保签名逻辑正确识别需要校验的头:
const signingOptions = { version: "v4", action: "write", expires: Date.now() + 15*60*1000, contentType: args.fileType, signedHeaders: ['content-type'], // 明确声明要校验的头 };
- 修正Fetch请求:移除错误的Origin头配置(浏览器会自动处理),并使用标准驼峰式的
Content-Type头名:
fetch(writeUrl, {method: "PUT", headers: {"Content-Type": fileElement.files[0].type}, body: fileElement.files[0]}) .then((response) => { // 执行操作 }) .catch((error) => { console.log(error); })
另外,若使用原生JS,代码中的Promise.await应改为await(需确保所在函数为async函数)。
内容的提问来源于stack exchange,提问作者gischer
相关产品推荐
相关产品推荐

