从使用Cloud Functions for Firebase上传的文件中获取下载URL

问题描述 投票:67回答:17

在Firebase存储中使用函数for Firebase上传文件后,我想获取该文件的下载URL。

我有这个 :

...

return bucket
    .upload(fromFilePath, {destination: toFilePath})
    .then((err, file) => {

        // Get the download url of file

    });

目标文件有很多参数。甚至一个名为mediaLink。但是,如果我尝试访问此链接,我会收到此错误:

匿名用户没有storage.objects.get访问对象...

有人可以告诉我如何获取公共下载Url?

谢谢

node.js firebase firebase-storage google-cloud-functions
17个回答
90
投票

您需要通过getSignedURL NPM模块使用@google-cloud/storage生成签名URL。

例:

const gcs = require('@google-cloud/storage')({keyFilename: 'service-account.json'});
// ...
const bucket = gcs.bucket(bucket);
const file = bucket.file(fileName);
return file.getSignedUrl({
  action: 'read',
  expires: '03-09-2491'
}).then(signedUrls => {
  // signedUrls[0] contains the file's public URL
});

您需要使用@google-cloud/storage初始化your service account credentials,因为应用程序默认凭据是不够的。

更新:云存储SDK现在可以通过Firebase Admin SDK访问,该SDK位于@ google-cloud / storage周围的acts as a wrapper。唯一的方法是你要么:

  1. 使用特殊服务帐户启动SDK,通常通过第二个非默认实例。
  2. 或者,如果没有服务帐户,则为默认的App Engine服务帐户提供“signBlob”权限。

3
投票

这是我目前使用的,它很简单,它完美无缺。

您无需对Google Cloud执行任何操作。它与Firebase开箱即用。

// Save the base64 to storage.
const file = admin.storage().bucket('url found on the storage part of firebase').file(`profile_photos/${uid}`);
await file.save(base64Image, {
    metadata: {
      contentType: 'image/jpeg',
    },
    predefinedAcl: 'publicRead'
});
const metaData = await file.getMetadata()
const url = metaData[0].mediaLinkenter

2
投票

如果您只需要一个包含简单URL的公共文件,则此方法有效。请注意,这可能会否决您的Firebase存储规则。

bucket.upload(file, function(err, file) {
    if (!err) {
      //Make the file public
      file.acl.add({
      entity: 'allUsers',
      role: gcs.acl.READER_ROLE
      }, function(err, aclObject) {
          if (!err) {
              var URL = "https://storage.googleapis.com/[your bucket name]/" + file.id;
              console.log(URL);
          } else {
              console.log("Failed to set permissions: " + err);
          }
      });  
    } else {
        console.log("Upload failed: " + err);
    }
});

2
投票

我有同样的问题,但是,我正在查看firebase函数示例的代码而不是README。并且这个帖子的答案也没有帮助......

您可以通过执行以下操作来避免传递配置文件:

转到项目的Cloud Console > IAM & admin > IAM,找到App Engine默认服务帐户,并将服务帐户令牌创建者角色添加到该成员。这将允许您的应用为图片创建签名的公共URL。

来源:Automatically Generate Thumbnails function README

您的应用引擎角色应如下所示:

Cloud Console


2
投票

此答案将总结在将文件上传到Google / Firebase云存储时获取下载URL的选项。下载URL有三种类型:

  1. 已签名的下载URL,这些URL是临时的,具有安全功能
  2. 令牌下载URL,具有持久性并具有安全功能
  3. 公共下载URL,持久且缺乏安全性

获取令牌下载URL有三种方法。其他两个下载URL只有一种方法可以获取它们。

从Firebase存储控制台

您可以从Firebase存储控制台获取下载URL:

enter image description here

下载URL如下所示:

https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Audio%2FEnglish%2FUnited_States-OED-0%2Fabout.mp3?alt=media&token=489c48b3-23fb-4270-bd85-0a328d2808e5

第一部分是文件的标准路径。最后是令牌。此下载URL是永久性的,即它不会过期,但您可以撤消它。

getDownloadURL()从前端

documentation告诉我们使用getDownloadURL()

let url = await firebase.storage().ref('Audio/English/United_States-OED-' + i +'/' + $scope.word.word + ".mp3").getDownloadURL();

这将获得与Firebase存储控制台相同的下载URL。这种方法很简单,但要求您知道文件的路径,在我的应用程序中,该文件的路径大约是300行代码,用于相对简单的数据库结构。如果您的数据库很复杂,这将是一场噩梦。您可以从前端上传文件,但这会将您的凭据公开给下载您应用的任何人。因此,对于大多数项目,您需要从Node后端或Google Cloud Functions上传文件,然后获取下载URL并将其与其他有关您文件的数据一起保存到数据库中。

用于临时下载URL的getSignedUrl()

getSignedUrl()易于从Node后端或Google Cloud功能使用:

  function oedPromise() {
    return new Promise(function(resolve, reject) {
      http.get(oedAudioURL, function(response) {
        response.pipe(file.createWriteStream(options))
        .on('error', function(error) {
          console.error(error);
          reject(error);
        })
        .on('finish', function() {
          file.getSignedUrl(config, function(err, url) {
            if (err) {
              console.error(err);
              return;
            } else {
              resolve(url);
            }
          });
        });
      });
    });
  }

签名的下载URL如下所示:

https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio%2FSpanish%2FLatin_America-Sofia-Female-IBM%2Faqu%C3%AD.mp3?GoogleAccessId=languagetwo-cd94d%40appspot.gserviceaccount.com&Expires=4711305600&Signature=WUmABCZIlUp6eg7dKaBFycuO%2Baz5vOGTl29Je%2BNpselq8JSl7%2BIGG1LnCl0AlrHpxVZLxhk0iiqIejj4Qa6pSMx%2FhuBfZLT2Z%2FQhIzEAoyiZFn8xy%2FrhtymjDcpbDKGZYjmWNONFezMgYekNYHi05EPMoHtiUDsP47xHm3XwW9BcbuW6DaWh2UKrCxERy6cJTJ01H9NK1wCUZSMT0%2BUeNpwTvbRwc4aIqSD3UbXSMQlFMxxWbPvf%2B8Q0nEcaAB1qMKwNhw1ofAxSSaJvUdXeLFNVxsjm2V9HX4Y7OIuWwAxtGedLhgSleOP4ErByvGQCZsoO4nljjF97veil62ilaQ%3D%3D

签名的URL具有到期日期和长签名。命令行gsutil signurl -d的文档说签名的URL是临时的:默认的到期时间是一小时,最长的到期时间是七天。

我要在这里咆哮,getSignedUrl从未说过你签名的网址会在一周内过期。文档代码将3-17-2025作为到期日期,建议您可以设置将来的到期年限。我的应用程序工作得很好,然后一周后崩溃了。错误消息表示签名不匹配,而不是下载URL已过期。我对我的代码进行了各种更改,一切正常......直到一周后它都崩溃了。这持续了一个多月的挫折。

使您的文件公开可用

您可以将文件的权限设置为公开读取,如documentation中所述。这可以从云存储浏览器或节点服务器完成。您可以将一个文件设为公用文件,也可以将目录设置为整个存储数这是节点代码:

var webmPromise = new Promise(function(resolve, reject) {
      var options = {
        destination: ('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.mp3'),
        predefinedAcl: 'publicRead',
        contentType: 'audio/' + audioType,
      };

      synthesizeParams.accept = 'audio/webm';
      var file = bucket.file('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm');
      textToSpeech.synthesize(synthesizeParams)
      .then(function(audio) {
        audio.pipe(file.createWriteStream(options));
      })
      .then(function() {
        console.log("webm audio file written.");
        resolve();
      })
      .catch(error => console.error(error));
    });

结果将在您的云存储浏览器中显示如下:

enter image description here

然后,任何人都可以使用标准路径下载您的文件:

https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio/English/United_States-OED-0/system.mp3

使文件公开的另一种方法是使用方法makePublic()。我无法让它工作,让存储桶和文件路径正确是很棘手的。

一个有趣的选择是使用Access Control Lists。您可以将文件仅供您放入列表的用户使用,或使用authenticatedRead将文件提供给从Google帐户登录的任何人。如果有选项“使用Firebase Auth登录我的应用程序的任何人”我会使用此选项,因为它会限制只访问我的用户。

使用firebase存储下载令牌构建您自己的下载URL

几个答案描述了未记录的Google Storage对象属性firebaseStorageDownloadTokens。通过此,您可以告诉存储您要使用的令牌。您可以使用uuid Node模块生成令牌。四行代码,您可以构建自己的下载URL,从控制台或getDownloadURL()获得的相同下载URL。四行代码是:

const uuidv4 = require('uuid/v4');
const uuid = uuidv4();
metadata: { firebaseStorageDownloadTokens: uuid }
https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm') + "?alt=media&token=" + uuid);

这是上下文中的代码:

var webmPromise = new Promise(function(resolve, reject) {
  var options = {
    destination: ('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.mp3'),
    contentType: 'audio/' + audioType,
    metadata: {
      metadata: {
        firebaseStorageDownloadTokens: uuid,
      }
    }
  };

      synthesizeParams.accept = 'audio/webm';
      var file = bucket.file('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm');
      textToSpeech.synthesize(synthesizeParams)
      .then(function(audio) {
        audio.pipe(file.createWriteStream(options));
      })
      .then(function() {
        resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent('Audio/' + longLanguage + '/' + pronunciation + '/' + word + '.webm') + "?alt=media&token=" + uuid);
      })
      .catch(error => console.error(error));
});

这不是一个错字 - 你必须在firebaseStorageDownloadTokens双层嵌套metadata:

Doug Stevenson指出firebaseStorageDownloadTokens不是Google云端存储的官方功能。您不会在任何Google文档中找到它,并且在将来的@google-cloud版本中没有任何承诺。我喜欢firebaseStorageDownloadTokens,因为它是获得我想要的唯一方法,但它有“气味”,使用起来不安全。

为什么没有来自Node的getDownloadURL()?

正如@Clinton所写,谷歌应该在file.getDownloadURL()(即你的Node后端)制作一个@google-cloud/storage方法。我想从Google Cloud Functions上传文件并获取令牌下载网址。


1
投票

对于使用Firebase SDK和admin.initializeApp的用户:

1 - Generate a Private Key并放在/ functions文件夹中。

2 - 按如下方式配置代码:

const serviceAccount = require('../../serviceAccountKey.json');
try { admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) })); } catch (e) {}

Documentation

try / catch是因为我使用的是index.js,它导入其他文件并为每个文件创建一个函数。如果您正在使用包含所有函数的单个index.js文件,那么您应该使用admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) }));


1
投票

从firebase 6.0.0开始,我可以直接通过管理员访问存储,如下所示:

const bucket = admin.storage().bucket();

所以我不需要添加服务帐户。然后设置上面引用的UUID用于获取firebase URL。


0
投票

如果您使用预定义的访问控制列表值'publicRead',您可以上传文件并使用非常简单的URL结构访问它:

// Upload to GCS
const opts: UploadOptions = {
  gzip: true,
  destination: dest, // 'someFolder/image.jpg'
  predefinedAcl: 'publicRead',
  public: true
};
return bucket.upload(imagePath, opts);

然后你可以像这样构建url:

const storageRoot = 'https://storage.googleapis.com/';
const bucketName = 'myapp.appspot.com/'; // CHANGE TO YOUR BUCKET NAME
const downloadUrl = storageRoot + bucketName + encodeURIComponent(dest);

0
投票

这是我提出的最好的。这是多余的,但唯一合理的解决方案对我有用。

await bucket.upload(localFilePath, {destination: uploadPath, public: true});
const f = await bucket.file(uploadPath)
const meta = await f.getMetadata()
console.log(meta[0].mediaLink)

47
投票

以下是有关如何在上传时指定下载令牌的示例:

const UUID = require("uuid-v4");

const fbId = "<YOUR APP ID>";
const fbKeyFile = "./YOUR_AUTH_FIlE.json";
const gcs = require('@google-cloud/storage')({keyFilename: fbKeyFile});
const bucket = gcs.bucket(`${fbId}.appspot.com`);

var upload = (localFile, remoteFile) => {

  let uuid = UUID();

  return bucket.upload(localFile, {
        destination: remoteFile,
        uploadType: "media",
        metadata: {
          contentType: 'image/png',
          metadata: {
            firebaseStorageDownloadTokens: uuid
          }
        }
      })
      .then((data) => {

          let file = data[0];

          return Promise.resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(file.name) + "?alt=media&token=" + uuid);
      });
}

然后打电话给

upload(localPath, remotePath).then( downloadURL => {
    console.log(downloadURL);
  });

这里的关键是在metadata选项属性中嵌套了一个metadata对象。将firebaseStorageDownloadTokens设置为uuid-v4值将告知云存储将其用作其公共身份验证令牌。

非常感谢@martemorfosis


16
投票

随着函数对象响应的最近更改,您可以获得将下载URL“拼接”在一起所需的一切,如下所示:

 const img_url = 'https://firebasestorage.googleapis.com/v0/b/[YOUR BUCKET]/o/'
+ encodeURIComponent(object.name)
+ '?alt=media&token='
+ object.metadata.firebaseStorageDownloadTokens;

console.log('URL',img_url);

16
投票

如果您正在使用Firebase项目,则可以在云功能中创建签名URL,而无需包含其他库或下载凭据文件。您只需启用IAM API并将角色添加到现有服务帐户(请参阅下文)。

初始化管理库并获得正常的文件引用:

import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'

admin.initializeApp(functions.config().firebase)

const myFile = admin.storage().bucket().file('path/to/my/file')

然后,您使用生成签名的URL

myFile.getSignedUrl({action: 'read', expires: someDateObj}).then(urls => {
    const signedUrl = urls[0]
})

确保您的Firebase服务帐户具有足够的权限来运行此帐户

  1. 转到Google API控制台并启用IAM API(https://console.developers.google.com/apis/api/iam.googleapis.com/overview
  2. 仍然在API控制台中,转到主菜单“IAM&admin” - >“IAM”
  3. 单击“App Engine默认服务帐户”角色的编辑
  4. 单击“添加其他角色”,然后添加名为“服务帐户令牌创建者”的角色
  5. 保存并等待一分钟以便传播更改

使用vanilla Firebase配置,第一次运行上面的代码时,您将收到错误身份和访问管理(IAM)API尚未在项目XXXXXX中使用之前或被禁用..如果您按照错误中的链接消息并启用IAM API,您将收到另一个错误:在服务帐户my-service-account上执行此操作需要权限iam.serviceAccounts.signBlob。添加令牌创建者角色修复了第二个权限问题。


11
投票

我成功使用的一种方法是在完成上传后将UUID v4值设置为文件元数据中名为firebaseStorageDownloadTokens的密钥,然后按照Firebase用于生成这些URL的结构自行组装下载URL,例如:

https://firebasestorage.googleapis.com/v0/b/[BUCKET_NAME]/o/[FILE_PATH]?alt=media&token=[THE_TOKEN_YOU_CREATED]

我不知道使用这种方法有多“安全”(考虑到Firebase可能会改变将来生成下载URL的方式),但它很容易实现。


11
投票

我建议在使用predefinedAcl: 'publicRead'或+上传文件时使用Cloud Storage NodeJS 1.6.x选项:

const options = {
    destination: yourFileDestination,
    predefinedAcl: 'publicRead'
};

bucket.upload(attachment, options);

然后,获取公共URL非常简单:

bucket.upload(attachment, options).then(result => {
    const file = result[0];
    return file.getMetadata();
}).then(results => {
    const metadata = results[0];
    console.log('metadata=', metadata.mediaLink);
}).catch(error => {
    console.error(error);
});

10
投票

对于那些想知道Firebase Admin SDK serviceAccountKey.json文件应该去哪里的人。只需将其放在函数文件夹中并照常部署即可。

它仍然让我感到困惑,为什么我们不能像在Javascript SDK中那样从元数据中获取下载URL。生成最终将过期并将其保存在数据库中的URL是不可取的。


8
投票

抱歉,由于缺少声誉,我无法在上面的问题上发表评论,所以我会将其包含在此答案中。

通过生成签名的Url,如上所述,但不是使用service-account.json我认为你必须使用你可以生成的serviceAccountKey.json(相应地替换YOURPROJECTID)

https://console.firebase.google.com/project/YOURPROJECTID/settings/serviceaccounts/adminsdk

例:

const gcs = require('@google-cloud/storage')({keyFilename: 'serviceAccountKey.json'});
// ...
const bucket = gcs.bucket(bucket);
// ...
return bucket.upload(tempLocalFile, {
        destination: filePath,
        metadata: {
          contentType: 'image/jpeg'
        }
      })
      .then((data) => {
        let file = data[0]
        file.getSignedUrl({
          action: 'read',
          expires: '03-17-2025'
        }, function(err, url) {
          if (err) {
            console.error(err);
            return;
          }

          // handle url 
        })

4
投票

我不能评论詹姆斯丹尼尔斯给出的答案,但我认为这非常重要。

给出一个签名的URL就像他在许多情况下看起来相当糟糕而且可能是Dangerous。根据Firebase的文档,签名的网址会在一段时间后过期,因此将其添加到您的数据库会在一段时间后导致空网址

可能是误解了那里的文档,并且签名的URL没有过期,结果会出现一些安全问题。对于每个上传的文件,密钥似乎都是相同的。这意味着一旦你获得了一个文件的URL,就可以通过知道他们的名字轻松访问他没有被访问过的文件。

如果我误解了那么我会很乐意纠正。否则有人应该更新上面提到的解决方案。如果我在那里可能是错的

© www.soinside.com 2019 - 2024. All rights reserved.