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上传会导致文件损坏。最终修复措施:
- 创建Storage实例时让库自动获取Cloud Run凭据,本地运行时通过ADC指定密钥
- 修复带尾随空格的存储桶名称环境变量(可能是主要原因)
- 使用正确的上传方法
- 修复前端上传流程的其他问题
修复后的代码:
后端(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
相关产品推荐
相关产品推荐

