使用Firebase ID令牌对Firebase存储进行身份验证

时间:2020-08-27 12:08:34

标签: firebase firebase-realtime-database firebase-authentication google-cloud-storage firebase-storage

我希望在服务器端尽可能清除凭证。因此,我使用Firebase Auth Rest Api向Firebase Auth进行身份验证。从请求中获得Firebase ID令牌,并使用此令牌按照Authenticate Rest Request (Authenticate with an ID token)的说明向Firebase Realtime Database发出请求。

问题是,当我尝试对Firebase Storage (Google Cloud Storage Request Endpoints)执行相同操作时,找不到任何避免将凭据存储在服务器端的解决方案(例如,使用Admin SDK,我可以写入或读取任何文件) ,但这意味着服务器的安全性将来可能出现问题,因为暴露了我的凭据)尽管在Authenticate Rest Request (Authenticate with an ID token)中明确指出:“当用户或设备使用Firebase身份验证登录时,Firebase会创建一个相应的ID令牌可以唯一地标识它们,并授予他们访问多种资源的权限,例如实时数据库和云存储。”

问题是:如何像使用Firebase Runtime Database一样使用Firebase ID令牌授权Firebase Storage Api Rest调用?

谢谢。

4 个答案:

答案 0 :(得分:2)

最后,我找到了答案的解决方案。解决方案是使用云功能。

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

这是功能代码:

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);

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

由于Firebase Storage实际上只是对Cloud Storage的重新包装,因此您可以使用Cloud Storage JSON API处理存储桶中的内容。从user account credentials的部分开始。您需要为Firebase Auth用户提供OAuth令牌,以与请求一起发送。

答案 3 :(得分:0)

由于目前尚无解决方案,我决定将二进制格式的文件存储在实时数据库中。 这样,我不需要在服务器中公开我的凭据,因为Firebase ID令牌可以进行身份​​验证。

相关问题