1

我想让我的服务器端尽可能地清除凭据。出于这个原因,我使用Firebase Auth Rest Api通过 Firebase Auth 对自己进行身份验证。从请求中,我获得了 firebase ID 令牌,并使用此令牌向 Firebase 实时数据库发出请求,如Authenticate Rest Request (Authenticate with an ID token)解释。

问题是,当我尝试对 Firebase 存储(谷歌云存储请求端点)做同样的事情时,我没有找到任何避免将凭据存储在服务器端的解决方案(例如,使用 Admin SDK 我可以写入或读取任何文件,但这意味着我的服务器未来可能会出现安全问题,因为我的凭据已暴露)尽管在Authenticate Rest Request (Authenticate with an ID token) 中明确表示:“当用户或设备使用 Firebase 身份验证登录时,Firebase 会创建相应的 ID唯一标识他们并授予他们访问多个资源的令牌,例如实时数据库和云存储。”

问题是:如何像使用 Firebase 运行时数据库一样使用 Firebase ID 令牌来授权 Firebase Storage Api Rest 调用?

谢谢。

4

4 回答 4

2

最后,我找到了答案的解决方案。解决方案是使用 Cloud Functions。

Cloud Functions 允许我们在作为我们 firebase 项目的一部分的 nodejs 环境中创建端点并使用 AdminSdk。使用这种方法,我们可以向该端点发送一个 http 请求,这将检查接收到的带有请求的令牌是否有效,如果有效,则保存文件。

这是功能代码:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const path = require("path");
const os = require("os");
const fs = require("fs");
const Busboy = require("busboy");

// Follow instructions to set up admin credentials:
// https://firebase.google.com/docs/functions/local-emulator#set_up_admin_credentials_optional
admin.initializeApp({
  credential: admin.credential.cert(
    __dirname + "/path/to/cert.json"
  ),
  storageBucket: "bucket-name",
});

const express = require("express");
const app = express();

// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const authenticate = async (req, res, next) => {
  if (
    !req.headers.authorization ||
    !req.headers.authorization.startsWith("Bearer ")
  ) {
    res.status(403).send("Unauthorized");
    return;
  }
  const idToken = req.headers.authorization.split("Bearer ")[1];
  try {
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    req.user = decodedIdToken;
    next();
    return;
  } catch (e) {
    res.status(403).send("Unauthorized");
    return;
  }
};

app.use(authenticate);

// POST /api/messages
// Create a new message, get its sentiment using Google Cloud NLP,
// and categorize the sentiment before saving.
app.post("/test", async (req, res) => {
  const busboy = new Busboy({ headers: req.headers });
  const tmpdir = os.tmpdir();

  // This object will accumulate all the fields, keyed by their name
  const fields = {};

  // This object will accumulate all the uploaded files, keyed by their name.
  const uploads = {};

  // This code will process each non-file field in the form.
  busboy.on("field", (fieldname, val) => {
    // TODO(developer): Process submitted field values here
    console.log(`Processed field ${fieldname}: ${val}.`);
    fields[fieldname] = val;
  });

  const fileWrites = [];

  // This code will process each file uploaded.
  busboy.on("file", (fieldname, file, filename) => {
    // Note: os.tmpdir() points to an in-memory file system on GCF
    // Thus, any files in it must fit in the instance's memory.
    console.log(`Processed file ${filename}`);
    const filepath = path.join(tmpdir, filename);
    uploads[fieldname] = filepath;

    const writeStream = fs.createWriteStream(filepath);
    file.pipe(writeStream);

    // File was processed by Busboy; wait for it to be written.
    // Note: GCF may not persist saved files across invocations.
    // Persistent files must be kept in other locations
    // (such as Cloud Storage buckets).
    const promise = new Promise((resolve, reject) => {
      file.on("end", () => {
        writeStream.end();
      });
      writeStream.on("finish", resolve);
      writeStream.on("error", reject);
    });
    fileWrites.push(promise);
  });

  // Triggered once all uploaded files are processed by Busboy.
  // We still need to wait for the disk writes (saves) to complete.
  busboy.on("finish", async () => {
    await Promise.all(fileWrites);
   
    // Process saved files here
    for (const file in uploads) {
      admin.storage().bucket().upload(uploads[file], function(err, file) {
        if (err) {
          res.status(403).send("Error saving the file.");
        }
        res.status(201).send("Saved");
      });
    }
  });

  busboy.end(req.rawBody);
});

exports.api = functions.https.onRequest(app);
于 2020-08-31T16:05:50.697 回答
0

如果您需要在服务器端访问 Firebase 存储,那么您将无法避免在某处存储凭据。您唯一能做的就是通过 API 请求将在客户端获得的用户凭据传递到您的服务器。虽然这不会给您带来任何安全优势,因为如果攻击者可以访问您的服务器,那么他无论如何都可以访问您的存储。

通常,将存储凭据存储在服务器上是安全的。无论如何,您需要使您的服务器尽可能安全。

于 2020-08-27T12:24:58.807 回答
0

由于 Firebase Storage 实际上只是对 Cloud Storage 的重新打包,因此您可以使用Cloud Storage JSON API来处理存储分区中的内容。从用户帐户凭据部分开始。您需要为 Firebase 身份验证用户提供 OAuth 令牌,以便随请求一起发送。

于 2020-08-27T15:45:38.690 回答
0

由于目前该问题没有解决方案,因此我决定将文件以二进制格式存储在实时数据库中。通过这种方式,我不需要在服务器中公开我的凭据,因为 Firebase ID 令牌会进行身份验证。

于 2020-08-27T18:15:03.937 回答