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

MERN栈应用Google Storage公网访问权限异常求助

Google Cloud Storage 公网访问权限问题排查

问题现象

文件上传至Google Storage成功,但获取公网URL时提示权限拒绝:

{
  "message": "Uploaded the file successfully: 9.png, but public access is denied!",
  "url": "https://storage.googleapis.com/devcret_filesnew/9.png"
}

已配置的存储桶权限

已为存储桶授予allUsers以下角色:

  • Storage Legacy Bucket Owner
  • Storage Legacy Bucket Reader
  • Storage Legacy Object Owner
  • Storage Legacy Object Reader
  • Storage Object Viewer

相关代码

Dashboard.jsx(前端上传逻辑)

...
const handleSubmit = async (e) => {
    e.preventDefault();
    let formData = new FormData();
    formData.append("file", file.data);
    const response = await authFetch.post(
      "/fileRoutes/image-upload",
      formData,
      {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      }
    );
    console.log(response.data.url);
  };
  const handleFileChange = (e) => {
    const img = {
      preview: URL.createObjectURL(e.target.files[0]),
      data: e.target.files[0],
    };
    setFile(img);
  };
return(
<form onSubmit={handleSubmit} method="post" encType="multipart/form-data">
   <input type="file" name="file" onChange={handleFileChange}></input>
   <button type="submit">Submit</button>
 </form>);

imgUpload.js(GCS初始化配置)

已将密钥JSON文件放在后端根目录下

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

const gcs = new Storage({
  projectId: "shaped-timing-xxx",
  keyFilename: "shaped-timingxxx.json",
});
const bucketName = "xxx";
export const bucket = gcs.bucket(bucketName);

function getPublicUrl(filename) {
  return "https://storage.googleapis.com/" + bucketName + "/" + filename;
}

let ImgUpload = {};

ImgUpload.uploadToGcs = (req, res, next) => {
  if (!req.file) return next();

  // Can optionally add a path to the gcsname below by concatenating it before the filename
  const gcsname = req.file.originalname;
  const file = bucket.file(gcsname);

  const stream = file.createWriteStream({
    metadata: {
      contentType: req.file.mimetype,
    },
  });

  stream.on("error", (err) => {
    req.file.cloudStorageError = err;
    next(err);
  });

  stream.on("finish", () => {
    req.file.cloudStorageObject = gcsname;
    req.file.cloudStoragePublicUrl = getPublicUrl(gcsname);
    next();
  });

  stream.end(req.file.buffer);
};

export default ImgUpload;

路由代码(后端上传处理)

const multer = Multer({
  storage: Multer.memoryStorage(),
  limits: {
    fileSize: 25 * 1024 * 1024, // no larger than 25MB, you can change as needed.
  },
});
router.post("/image-upload", multer.single("file"), (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).send({ message: "Please upload a file!" });
    }

    const blob = bucket.file(req.file.originalname);
    const blobStream = blob.createWriteStream({
      resumable: false,
    });

    blobStream.on("error", (err) => {
      res.status(500).send({ message: err.message });
    });

    blobStream.on("finish", async (data) => {
      // create a url to access file
      const publicURL = format(
        `https://storage.googleapis.com/${bucket.name}/${blob.name}`
      );

      try {
        await bucket.file(req.file.originalname).makePublic();
      } catch {
        return res.status(500).send({
          message: `Uploaded the file successfully: ${req.file.originalname}, but public access is denied!`,
          url: publicURL,
        });
      }

      res.status(200).send({
        message: "Uploaded the file successfully: " + req.file.originalname,
        url: publicURL,
      });
    });
    blobStream.end(req.file.buffer);
  } catch (err) {
    if (err.code == "LIMIT_FILE_SIZE") {
      return res.status(500).send({
        message: "File size cannot be larger than 25MB!",
      });
    }

    res.status(500).send({
      message: `Could not upload the file: ${req.file.originalname}. ${err}`,
    });
  }
});

排查与修复方案

1. 关闭存储桶公共访问阻止政策

Google Cloud默认会强制阻止存储桶公共访问,即使配置了allUsers权限也会失效:

  • 进入Google Cloud控制台的存储桶页面
  • 切换到「权限」标签页,查看顶部是否有「公共访问被阻止」提示
  • 点击「编辑」,关闭公共访问阻止(需确认业务风险)

2. 捕获makePublic具体错误

当前代码未捕获makePublic的详细错误,修改代码打印错误信息,定位具体问题:

try {
  await bucket.file(req.file.originalname).makePublic();
} catch (err) {
  console.error('Public access error:', err); // 打印错误详情
  return res.status(500).send({
    message: `Uploaded the file successfully: ${req.file.originalname}, but public access is denied! Error: ${err.message}`,
    url: publicURL,
  });
}

3. 验证服务账号权限

确保后端使用的服务账号(对应shaped-timingxxx.json)拥有Storage Object Admin或Storage Admin角色,否则无法执行makePublic修改对象权限的操作:

  • 进入Google Cloud控制台IAM页面,找到该服务账号,确认角色配置

4. 直接设置对象ACL替代makePublic

如果makePublic仍失败,尝试直接设置对象的公共读权限:

// 替换原makePublic代码
await bucket.file(req.file.originalname).setACL('public-read');

5. 避免文件名冲突

若上传的文件名已存在,新文件可能继承旧文件的私有权限。建议使用唯一文件名(如用户ID+时间戳+原文件名):

const uniqueFilename = `${req.user.id}-${Date.now()}-${req.file.originalname}`;
const blob = bucket.file(uniqueFilename);
// 后续代码统一使用uniqueFilename

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:13:16