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

GCP Cloud Run部署后SignedUrl签名不匹配致访问被拒

问题:Next.js应用部署到Cloud Run后,GCS生成的SignedUrl出现签名不匹配错误

我有一个基于Next.js的应用,使用Google Cloud Storage Node.js SDK上传视频并生成SignedUrl供客户端查看。本地运行一切正常,但部署到Cloud Run后,所有存储桶操作都无法正常工作,尤其是生成SignedUrl的功能。后端生成SignedUrl后,客户端访问时出现如下错误:

<Code>SignatureDoesNotMatch</Code>
<Message>Access denied.</Message>
<Details>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Details>

已尝试的排查步骤

  • 将完整的服务账号密钥编码为base64并作为环境变量使用,本地运行正常
  • 反复核对GCP与本地的密钥环境变量值
  • 将SDK更新至最新版本(7.11.0)
  • 为运行服务的服务账号授予“Service Account Token Creator”角色
  • 调整存储桶CORS配置,尝试过“*”和自定义域名,本地均正常
  • 尝试修改生成URL函数的内容类型,但出现新错误
  • 创建Storage实例时移除环境变量注入,让GCP自动提供凭据,仍报错
  • 本地使用ADC模拟服务账号,运行正常
  • 使用新服务账号搭建极简Express服务器,同样本地正常、部署后签名不匹配

原代码实现

import { Storage } from "@google-cloud/storage";
import { env } from "process";

const rawKey = env.GCP_BUCKET_HANDLER_KEY ?? "";

const creds = rawKey
    ? JSON.parse(Buffer.from(rawKey, "base64").toString())
    : {};

const storage = new Storage({
    projectId: creds.project_id,
    credentials: creds,
});

const PRIMARY_BUCKET_NAME = env.GCP_PRIMARY_BUCKET_NAME ?? "invalid";

export const primaryBucket = storage.bucket(PRIMARY_BUCKET_NAME);

export async function gcGenerateReadSignedUrl({
    fileName,
    id,
}: GcVideoFilePathProps) {
    const filePath = `video/${id}/${fileName}`;
    const options: GetSignedUrlConfig = {
        version: "v4",
        action: "read",
        expires: Date.now() + 15 * 60 * 1000, // 15 minutes
    };
    const [url] = await primaryBucket.file(filePath).getSignedUrl(options);
    return url;
}

编辑

搭建Go服务器生成SignedUrl在部署环境中正常工作,使用的服务账号与Next.js应用相同,曾考虑将存储桶操作迁移至Go服务器。

编辑2:修复方案

后续部署Go服务器时发现环境变量注入问题,进而发现存储桶名称环境变量存在尾随空格;同时发现使用signedPostPolicyV4结合formData上传会导致文件损坏。最终修复措施:

  1. 创建Storage实例时让库自动获取Cloud Run凭据,本地运行时通过ADC指定密钥
  2. 修复带尾随空格的存储桶名称环境变量(可能是主要原因)
  3. 使用正确的上传方法
  4. 修复前端上传流程的其他问题

修复后的代码:

后端(Next.js)

const options = {
    version: "v4",
    action: "write",
    expires: Date.now() + 15 * 60 * 1000, // 15 minutes
} satisfies GetSignedUrlConfig;
const [url] = await primaryBucket.file(filePath).getSignedUrl(options);

前端(Next.js)

const upload = await fetch(url, {
    method: "PUT",
    body: file,
});

现在初始的Next.js方案在生产环境中运行正常,多个小问题叠加导致难以定位,通过搭建测试服务器缩小了排查范围。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:21:12