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

如何让GCP App Engine(Node.js)无需公开共享访问Cloud Storage?

解决方案:App Engine 私有访问 Google Cloud Storage 资产

你的需求非常合理——既要把资产移到GCS,又要保证只有认证用户能访问,同时避免公开存储桶或者给每个用户单独配置IAM权限。下面是具体的实现步骤和代码示例:

核心思路

App Engine 默认使用的服务账号可以被授予 GCS 存储桶的读取权限,然后在你的 Node.js 应用中,先通过 Passport-SAML 验证用户身份,验证通过后再通过两种方式之一提供资产:

  1. 应用作为代理,从 GCS 读取文件后返回给用户
  2. 生成签名URL,让用户直接访问 GCS(URL 有过期时间,只有认证用户能获取)

步骤1:配置 App Engine 服务账号的 GCS 权限

首先需要给 App Engine 的默认服务账号分配读取存储桶的权限:

  • 找到你的 App Engine 服务账号,格式为 [你的项目ID]@appspot.gserviceaccount.com
  • 打开 GCP 控制台 → 存储 → 存储桶 → 目标存储桶 → 「权限」标签
  • 点击「添加成员」,输入上述服务账号邮箱
  • 在「角色」下拉菜单中选择「Cloud Storage → 存储对象查看器」(roles/storage.objectViewer)
  • 保存配置,这样 App Engine 就有权限读取该存储桶内的文件了

步骤2:选择并实现访问方案

方案一:应用代理模式(适合小文件,如图片)

这种模式下,应用先验证用户身份,然后从 GCS 读取文件,再流式返回给用户。优点是完全由应用控制访问逻辑,缺点是大文件会占用应用的带宽和资源。

代码示例:

首先安装依赖:

npm install @google-cloud/storage

然后在你的 Express 应用中添加路由:

const { Storage } = require('@google-cloud/storage');
const storage = new Storage();
const bucketName = '你的存储桶名称';

// 假设你已经配置好了 Passport-SAML 的验证中间件
app.get('/protected-assets/:filename', ensureAuthenticated, async (req, res) => {
  const filename = req.params.filename;
  
  try {
    const file = storage.bucket(bucketName).file(filename);
    // 先检查文件是否存在
    const [exists] = await file.exists();
    if (!exists) {
      return res.status(404).send('文件不存在');
    }

    // 获取文件元数据,设置正确的 Content-Type
    const [metadata] = await file.getMetadata();
    res.set('Content-Type', metadata.contentType);
    
    // 流式传输文件到响应
    file.createReadStream().pipe(res);
  } catch (error) {
    console.error('访问 GCS 文件出错:', error);
    res.status(500).send('服务器内部错误');
  }
});

// Passport-SAML 的身份验证中间件示例
function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  // 未认证则跳转到登录页
  res.redirect('/saml/login');
}

方案二:签名URL模式(适合大文件,如视频)

这种模式下,应用验证用户身份后,生成一个有过期时间的签名URL返回给前端,用户可以直接用这个URL访问GCS。优点是节省应用资源,大文件直接从GCDN分发,缺点是URL有有效期,需要前端处理过期逻辑。

代码示例:

app.get('/protected-assets/signed-url/:filename', ensureAuthenticated, async (req, res) => {
  const filename = req.params.filename;
  const file = storage.bucket(bucketName).file(filename);

  try {
    const [exists] = await file.exists();
    if (!exists) {
      return res.status(404).send('文件不存在');
    }

    // 生成有效期1小时的签名URL
    const [signedUrl] = await file.getSignedUrl({
      action: 'read',
      expires: Date.now() + 3600 * 1000, // 1小时后过期
    });

    // 返回签名URL给前端
    res.json({ signedUrl });
  } catch (error) {
    console.error('生成签名URL出错:', error);
    res.status(500).send('服务器内部错误');
  }
});

关键注意事项

  • 存储桶权限:确保存储桶的默认权限是私有,不要设置任何「公开访问」的权限
  • 权限最小化:只给 App Engine 服务账号分配 storage.objectViewer 角色,避免过度授权
  • 签名URL有效期:根据你的业务需求调整过期时间,比如15分钟到1小时,避免URL长期有效带来的安全风险
  • 流式传输:代理模式下一定要用流式传输,不要把整个文件读入内存,避免内存溢出
  • 身份验证:确保 ensureAuthenticated 中间件能正确拦截未认证的请求,只有通过 Passport-SAML 验证的用户才能获取资产

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:13:46