developer tip

Firebase 용 Cloud Functions로 업로드 된 파일에서 다운로드 URL 가져 오기

copycodes 2020. 10. 6. 08:23
반응형

Firebase 용 Cloud Functions로 업로드 된 파일에서 다운로드 URL 가져 오기


Firebase 용 함수로 Firebase 저장소에 파일을 업로드 한 후 파일의 다운로드 URL을 가져오고 싶습니다.

나는 이것을 가지고있다 :

...

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

        // Get the download url of file

    });

개체 파일에는 많은 매개 변수가 있습니다. 이름이 mediaLink. 그러나이 링크에 액세스하려고하면 다음 오류가 발생합니다.

익명 사용자에게는 storage.objects.get 액세스 권한이 없습니다.

누군가 공개 다운로드 URL을 얻는 방법을 알려줄 수 있습니까?

감사합니다


@ google-cloud / storage NPM 모듈을 통해 getSignedURL사용하여 서명 된 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함께 서비스 계정 자격 증명 응용 프로그램의 기본 자격 증명이 충분하지 않으므로.

업데이트 : 이제 @ google-cloud / storage 의 래퍼 역할을하는 Firebase Admin SDK를 통해 Cloud Storage SDK에 액세스 할 수 있습니다 . 유일한 방법은 다음과 같은 경우입니다.

  1. 일반적으로 기본이 아닌 두 번째 인스턴스를 통해 특수 서비스 계정으로 SDK를 초기화합니다.
  2. 또는 서비스 계정없이 기본 App Engine 서비스 계정에 'signBlob'권한을 부여합니다.

업로드시 다운로드 토큰을 지정하는 방법에 대한 예는 다음과 같습니다.

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

여기서 핵심 metadatametadata옵션 속성 내에 중첩 된 개체 가 있다는 것입니다 . firebaseStorageDownloadTokensuuid-v4 값으로 설정 하면 Cloud Storage가이를 공개 인증 토큰으로 사용하도록 지시합니다.

@martemorfosis 덕분에


Firebase 프로젝트에서 작업하는 경우 다른 라이브러리를 포함하거나 사용자 인증 정보 파일을 다운로드하지 않고도 Cloud Function에서 서명 된 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 및 관리자"-> "IAM"으로 이동합니다.
  3. 'App Engine 기본 서비스 계정'역할에 대한 수정을 클릭합니다.
  4. "다른 역할 추가"를 클릭하고 "서비스 계정 토큰 생성자"라는 역할을 추가합니다.
  5. 저장하고 변경 사항이 전파 될 때까지 잠시 기다리십시오.

바닐라 Firebase 구성을 사용하면 위 코드를 처음 실행하면 ID 및 액세스 관리 (IAM) API가 이전에 프로젝트 XXXXXX에서 사용되지 않았거나 사용 중지되었다는 오류 가 표시됩니다. . 오류 메시지의 링크를 따라 IAM API를 활성화하면 또 다른 오류가 발생합니다. 서비스 계정 my-service-account에서이 작업을 수행하려면 권한 iam.serviceAccounts.signBlob이 필요합니다 . 토큰 생성자 역할을 추가하면이 두 번째 권한 문제가 해결됩니다.


함수 객체 응답 의 최근 변경으로 다음 과 같이 다운로드 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);

Cloud Storage NodeJS 1.6.x 또는 + predefinedAcl: 'publicRead'로 파일을 업로드 할 때 옵션을 사용하는 것이 좋습니다 .

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

이 답변은 Google / Firebase Cloud Storage에 파일을 업로드 할 때 다운로드 URL을 가져 오는 옵션을 요약합니다. 다운로드 URL에는 세 가지 유형이 있습니다.

  1. 임시적이며 보안 기능이있는 서명 된 다운로드 URL
  2. 영구적이고 보안 기능이있는 토큰 다운로드 URL
  3. 영구적이고 보안이 부족한 공개 다운로드 URL

토큰 다운로드 URL을 얻는 방법에는 세 가지가 있습니다. 다른 두 개의 다운로드 URL에는이를 가져올 수있는 방법이 하나뿐입니다.

Firebase 저장소 콘솔에서

Firebase 저장소 콘솔에서 다운로드 URL을 가져올 수 있습니다.

여기에 이미지 설명 입력

다운로드 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 ()

문서 사용을 우리에게 알려줍니다 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 Functions에서 사용하기 쉽습니다.

  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은 임시적입니다. 기본 만료 시간은 1 시간이고 최대 만료 시간은 7 일입니다.

여기서 getSignedUrl 은 서명 된 URL이 1 주일 후에 만료된다고 결코 말하지 않는다는 사실을 폭언 할 것입니다. 문서 코드에는 3-17-2025만료 날짜가 있으므로 향후 만료 연도를 설정할 수 있음을 나타냅니다. 내 앱은 완벽하게 작동했지만 일주일 후 충돌했습니다. 오류 메시지는 다운로드 URL이 만료 된 것이 아니라 서명이 일치하지 않는다는 내용입니다. 나는 내 코드를 다양하게 변경했고 모든 것이 작동했다. 이것은 한 달 이상 좌절감으로 계속되었습니다.

파일을 공개적으로 사용 가능하게 만들기

문서에 설명 된대로 파일에 대한 권한을 공개 읽기로 설정할 수 있습니다 . 이는 Cloud Storage 브라우저 또는 노드 서버에서 수행 할 수 있습니다. 하나의 파일을 공개하거나 디렉토리 또는 전체 스토리지 데이터베이스를 만들 수 있습니다. 다음은 노드 코드입니다.

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

결과는 Cloud Storage 브라우저에서 다음과 같이 표시됩니다.

여기에 이미지 설명 입력

그러면 누구나 표준 경로를 사용하여 파일을 다운로드 할 수 있습니다.

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

파일을 공개하는 또 다른 방법은 makePublic () 메서드를 사용하는 것 입니다. 이 작업을 수행 할 수 없었고 버킷 및 파일 경로를 올바르게 지정하는 것이 까다 롭습니다.

흥미로운 대안은 액세스 제어 목록 을 사용하는 것 입니다. 목록에 추가 한 사용자 만 파일을 사용할 수 있도록하거나 authenticatedReadGoogle 계정으로 로그인 한 모든 사용자가 파일을 사용할 수 있도록 할 수 있습니다. "Firebase Auth를 사용하여 내 앱에 로그인 한 모든 사람"옵션이있는 경우이 옵션을 사용하면 내 사용자 만 액세스 할 수 있습니다.

firebaseStorageDownloadTokens로 나만의 다운로드 URL 구축

몇 가지 답변은 문서화되지 않은 Google Storage 개체 속성을 설명합니다 firebaseStorageDownloadTokens. 이를 통해 사용하려는 토큰을 Storage에 알릴 수 있습니다. uuidNode 모듈 로 토큰을 생성 할 수 있습니다 . 네 줄의 코드를 사용하면 콘솔 또는 .NET에서 가져온 것과 동일한 다운로드 URL 인 고유 한 다운로드 URL을 만들 수 있습니다 getDownloadURL(). 네 줄의 코드는 다음과 같습니다.

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 Cloud Storage 기능이 아니라고 지적했습니다 . Google 문서에서 찾을 수 없으며의 향후 버전에 포함될 것이라는 약속도 없습니다 @google-cloud. 내가 원하는 firebaseStorageDownloadTokens것을 얻을 수있는 유일한 방법이기 때문에 좋아 하지만 사용하기에 안전하지 않다는 "냄새"가 있습니다.

Node에 getDownloadURL ()이없는 이유는 무엇입니까?

@Clinton이 썼 듯이 Google은 (즉, 노드 백엔드) file.getDownloadURL()에서 메소드를 만들어야합니다 @google-cloud/storage. Google Cloud Functions에서 파일을 업로드하고 토큰 다운로드 URL을 받고 싶습니다.


Firebase Admin SDK serviceAccountKey.json 파일이 어디로 이동해야하는지 궁금한 분들을 위해. functions 폴더에 넣고 평소대로 배포하십시오.

Javascript SDK 에서처럼 메타 데이터에서 다운로드 URL을 가져올 수없는 이유는 여전히 당황 스럽습니다. 결국 만료 될 URL을 생성하여 데이터베이스에 저장하는 것은 바람직하지 않습니다.


내가 성공적으로 사용하는 한 가지 방법은 firebaseStorageDownloadTokens업로드가 완료된 후 UUID v4 값을 파일의 메타 데이터에 명명 키로 설정 한 다음 Firebase가 이러한 URL을 생성하는 데 사용하는 구조에 따라 직접 다운로드 URL을 조합하는 것입니다. 예 :

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

이 방법을 사용하는 것이 얼마나 "안전한지"는 모르겠지만 (Firebase가 향후 다운로드 URL을 생성하는 방법을 변경할 수 있다는 점을 감안하면) 구현하기 쉽습니다.


죄송하지만 위의 질문에 평판이 없어 댓글을 달 수 없으므로이 답변에 포함하겠습니다.

서명 된 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 
        })

나는 제임스 다니엘스가 준 대답에 대해 언급 할 수 없지만 이것은 읽는 것이 매우 중요하다고 생각합니다.

그가 한 것처럼 서명 된 URL을 제공하는 것은 많은 경우에 매우 나쁘고 위험 할 수있는 것처럼 보입니다 . Firebase 문서에 따르면 서명 된 URL은 일정 시간이 지나면 만료되므로이를 databse에 추가하면 특정 시간이 지나면 빈 URL이 생성됩니다.

문서를 오해하고 서명 된 URL이 만료되지 않아 결과적으로 보안 문제가 발생할 수 있습니다. 키는 업로드 된 모든 파일에 대해 동일한 것 같습니다. 즉, 한 파일의 URL을 얻은 후에는 누군가 이름 만 알면 액세스 할 수없는 파일에 쉽게 액세스 할 수 있습니다.

내가 그것을 이해하지 못한다면 나는 바로 잡아야 할 것이다. 그렇지 않으면 아마도 위에 명명 된 솔루션을 업데이트해야합니다. 내가 틀렸다면


이것이 제가 현재 사용하고있는 것입니다. 간단하고 완벽하게 작동합니다.

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

이것은 단순한 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);
    }
});

I had the same issue, however, I was looking at the code of the firebase function example instead of the README. And Answers on this thread didn't help either...

You can avoid passing the config file by doing the following:

Go to your project's Cloud Console > IAM & admin > IAM, Find the App Engine default service account and add the Service Account Token Creator role to that member. This will allow your app to create signed public URLs to the images.

source: Automatically Generate Thumbnails function README

Your role for app engine should look like this:

클라우드 콘솔


For those who are using Firebase SDK andadmin.initializeApp:

1 - Generate a Private Key and place in /functions folder.

2 - Configure your code as follows:

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

Documentation

The try/catch is because I'm using a index.js that imports other files and creates one function to each file. If you're using a single index.js file with all functions, you should be ok with admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) }));.


As of firebase 6.0.0 I was able to access the storage directly with the admin like this:

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

So I didn't need to add a service account. Then setting the UUID as referenced above worked for getting the firebase url.


If you use the predefined access control lists value of 'publicRead', you can upload the file and access it with a very simple url structure:

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

You can then construct the url like so:

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

This is the best I came up. It is redundant, but the only reasonable solution that worked for me.

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

If you are getting error:

Google Cloud Functions: require(…) is not a function

try this:

const {Storage} = require('@google-cloud/storage');
const storage = new Storage({keyFilename: 'service-account-key.json'});
const bucket = storage.bucket(object.bucket);
const file = bucket.file(filePath);
.....

Without signedURL() using makePublic()

const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp()
var bucket = admin.storage().bucket();

// --- [Above] for admin related operations, [Below] for making a public url from a GCS uploaded object

const { Storage } = require('@google-cloud/storage');
const storage = new Storage();

exports.testDlUrl = functions.storage.object().onFinalize(async (objMetadata) => {
    console.log('bucket, file', objMetadata.bucket + ' ' + objMetadata.name.split('/').pop()); // assuming file is in folder
    return storage.bucket(objMetadata.bucket).file(objMetadata.name).makePublic().then(function (data) {
        return admin.firestore().collection('publicUrl').doc().set({ publicUrl: 'https://storage.googleapis.com/' + objMetadata.bucket + '/' + objMetadata.name }).then(writeResult => {
            return console.log('publicUrl', writeResult);
        });
    });
});

참고 URL : https://stackoverflow.com/questions/42956250/get-download-url-from-file-uploaded-with-cloud-functions-for-firebase

반응형