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

通过预签名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,请问问题根源是什么?该如何解决?


问题根源与解决方法

问题根源

  1. 签名与请求头的大小写校验冲突:GCS V4签名会将你指定的contentType转换为小写的content-type加入签名校验列表,但浏览器发送请求时会自动将请求头标准化为驼峰式的Content-Type。虽然HTTP头本身不区分大小写,但GCS的签名校验逻辑会严格对比签名中声明的头名与实际请求的头名,最终判定content-type头未被发送。
  2. 无效的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,需确保签名与请求的头信息完全匹配:

  1. 修正签名配置:明确指定signedHeaders为content-type,确保签名逻辑正确识别需要校验的头:
const signingOptions = {
    version: "v4",
    action: "write",
    expires: Date.now() + 15*60*1000,
    contentType: args.fileType,
    signedHeaders: ['content-type'], // 明确声明要校验的头
};
  1. 修正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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:59:57