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

使用Go生成GCP presignURL上传图片时遇SignatureDoesNotMatch错误

GCS预签名URL上传出现SignatureDoesNotMatch错误的解决办法

问题场景

我用Go代码通过预签名URL上传图片到Google Cloud Storage(GCS)时,持续收到SignatureDoesNotMatch的403错误。已经给目标存储桶配置了拥有Storage Admin和Storage Object Admin权限的服务账号,下载了对应的JSON密钥文件,生成预签名URL时明确指定了Content-Type: image/jpeg。

错误原因

排查后发现,上传请求中使用了multipart.NewWriter来构建请求体,这导致实际发送的请求头里Content-Type被自动设置为multipart/form-data,和生成预签名URL时指定的image/jpeg不一致——GCS的预签名URL验证要求请求的关键参数(包括Content-Type)必须和签名时完全匹配,参数不符就会触发签名验证失败,返回403错误。

解决方案

修改上传代码,直接以原始文件的image/jpeg类型发送请求,不再使用multipart格式,确保请求的Content-Type和生成预签名URL时的参数一致。

修正后的代码示例

生成预签名URL的代码

package main

import (
	"context"
	"fmt"
	"time"

	"cloud.google.com/go/storage"
	"google.golang.org/api/option"
)

func generatePresignedURL(bucket, object string) (string, error) {
	ctx := context.Background()
	client, err := storage.NewClient(ctx, option.WithCredentialsFile("service-account-key.json"))
	if err != nil {
		return "", fmt.Errorf("storage.NewClient: %v", err)
	}
	defer client.Close()

	opts := &storage.SignedURLOptions{
		GoogleAccessID: "your-service-account@your-project.iam.gserviceaccount.com",
		PrivateKey:     []byte("your-private-key"), // 可从密钥文件中读取
		Method:         "PUT",
		Expires:        time.Now().Add(15 * time.Minute),
		ContentType:    "image/jpeg", // 明确指定与上传一致的Content-Type
	}

	url, err := client.Bucket(bucket).SignedURL(object, opts)
	if err != nil {
		return "", fmt.Errorf("Bucket(%q).SignedURL: %v", bucket, err)
	}
	return url, nil
}

上传文件的代码

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func uploadFile(presignedURL, filePath string) error {
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	req, err := http.NewRequest("PUT", presignedURL, file)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "image/jpeg") // 严格匹配签名时的Content-Type

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("upload failed: %s", resp.Status)
	}
	return nil
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 02:41:06