用户已获得授权,但出现了“ Firebase存储:用户无权访问'路径'”错误

时间:2018-11-17 20:47:56

标签: angular firebase google-cloud-firestore firebase-storage angularfire

我已经在我的应用中实现了身份验证,并且在创建用户和进行身份验证时没有任何问题。。但是,现在我正在尝试将文件上传到Firebase Storage,但这只会当我删除身份验证规则并使访问公开时,它可以工作。如果我保留默认规则,仅允许经过身份验证的用户访问(这是我想要的),则会收到错误消息:Firebase Storage: User does not have permission to access 'profile-image/test.PNG'.我调用了一个方法,以在放置请求之前验证我的身份验证状态,并且可以读取/ write到firestore数据库没有问题,所以我确定我已经通过身份验证。

我是一个完整的FNG,所以很有可能该问题是我已经完成/尚未完成的事情。如果相关,我正在使用Angular。我还使用Google Cloud Platform激活了一个计费帐户,但这并没有改变。

这是我的控制台日志,其中显示了我使用的引用,我尝试添加的文件(同样,当我公开访问时,这两个文件都可以正常工作),来自身份验证状态调用的uid和错误:

STARTING UPLOAD SERVICE                        upload.service.ts:26
FIREBASE STORAGE REFERENCE:                    upload.service.ts:27 
Reference {authWrapper: AuthWrapper, location: Location}
  authWrapper: AuthWrapper {bucket_: "my-app.appspot.com", deleted_: false, app_: FirebaseAppImpl, storageRefMaker_: ƒ, requestMaker_: ƒ, …}
bucket: (...)
fullPath: (...)
location: Location {bucket: "my-app.appspot.com", path_: "profile-image/test.PNG"}
name: (...)
parent: (...)
root: (...)
storage: (...)
__proto__: Object
                                                 upload.service.ts:28 
FILE CONTENTS:
                                                 upload.service.ts:29 
File(286831) {name: "test.PNG", lastModified: 1542480795011, lastModifiedDate: Sat Nov 17 2018 13:53:15 GMT-0500 (Eastern Standard Time), webkitRelativePath: "", size: 286831, …}
                                                 upload.service.ts:24 
USER AUTHENTICATED: Er6sWsDvEjM69WBAKxQffcbdPZG2
POST https://firebasestorage.googleapis.com/v0/b/{my-app-name}/o?name=profile-image%2Ftest.PNG 403
                                                 upload.service.ts:33  
FirebaseStorageError {code_: "storage/unauthorized", message_: "Firebase Storage: User does not have permission to access 'profile-image/test.PNG'.", 
serverResponse_: "{↵  "error": {↵    "code": 403,↵    "message": "Pe…n denied. Could not perform this operation"↵  }↵}", name_: "FirebaseError"}
code: (...)
code_: "storage/unauthorized"
message: (...)
message_: "Firebase Storage: User does not have permission to access 'profile-image/test.PNG'."
name: (...)
name_: "FirebaseError"
serverResponse: (...)
serverResponse_: "{↵  "error": {↵    "code": 403,↵    "message": "Permission denied. Could not perform this operation"↵  }↵}"
__proto__: Object 

Firebase存储规则

service firebase.storage {
 match /b/my-app.appspot.com/o {
  match /{allPaths=**} {
   allow read, write: if request.auth != null;
  }
 }
}  

(我还尝试了request.auth.uid!= null,但这没有什么区别。)

我的上传服务:

import { Injectable } from '@angular/core';
import * as firebase from 'firebase/app';
import 'firebase/storage';
import { AuthService } from '../services/auth.service';


@Injectable({
  providedIn: 'root'
})
export class UploadService {

  constructor(
    private authService: AuthService
  ) { }

  pushUpload(uploadFile: File) {
    console.log("STARTING UPLOAD SERVICE")
    var storage = firebase.storage();
    var storageRef = storage.ref();
    var profileRef = storageRef.child('profile-image');
    var docRef = profileRef.child(uploadFile.name);

    this.authService.getAuthState().subscribe(auth => {
      console.log("USER AUTHENTICATED: " + auth.uid);
    })
    console.log("FIREBASE STORAGE REFERENCE:")
    console.log(docRef);
    console.log("FILE CONTENTS:");
    console.log(uploadFile);
    docRef.put(uploadFile).then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    });
  }
}

environment.ts中的Firebase配置:

import * as fb from 'firebase/app';

// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.

export const environment = {
  production: false,
  firebase: {
    apiKey: "{my-api-key}",
    authDomain: "my-app.firebaseapp.com",
    databaseURL: "my-app.firebaseio.com",
    projectId: "my-app",
    storageBucket: "gs://my-app.appspot.com",
    messagingSenderId: "####"
  }
};

fb.initializeApp(environment.firebase);

我在控制台日志和environment.ts文件中用通用替换了一些标识信息。 我还应该提到,在添加fb.initializeApp(environment.firebase);之前,身份验证对我来说一切正常,但是尝试提交上传请求后,如果没有此行,则会出现错误。

非常感谢您提供的任何建议,如果我需要提供更多信息,请告诉我!

4 个答案:

答案 0 :(得分:0)

allow read, write: if request.auth != null;

意味着,您必须先登录才能在此处写信。

只需尝试使用==一次,您就会发现它可行。

答案 1 :(得分:0)

事实证明,使用Angular确实很重要。我需要像这样将AngularFireStorage添加到app.module.ts

import { AngularFireStorage } from '@angular/fire/storage'; 

@NgModule({
....
providers: [..., AngularFireStorage]

,然后还必须导入到我的uploads.component.ts:

import { AngularFireStorage, AngularFireUploadTask } from '@angular/fire/storage';

,然后我完全废弃了我的UploadService,并抓取了此人教程的一部分:https://angularfirebase.com/lessons/firebase-storage-with-angularfire-dropzone-file-uploader/,该教程使用AngularFireUploadTask和一些Observables真正轻松地完成了整个上传过程。因此,这就是我在uploads.component.ts中最终得到的结果方法:

import { Component, OnInit, Input } from '@angular/core';
import { AngularFireStorage, AngularFireUploadTask } from '@angular/fire/storage';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-uploads',
  templateUrl: './uploads.component.html',
  styleUrls: ['./uploads.component.css']
})
export class UploadsComponent implements OnInit {
  @Input() uploadFolder: string;  //the folder to save this particular upload to in the Storage Bucket   
  selectedFile: File;
  task: AngularFireUploadTask;  // Main task 
  percentage: Observable<number>;  // Progress monitoring
  snapshot: Observable<any>;// Progress monitoring


  constructor(
    private storage: AngularFireStorage
    ) { }

  ngOnInit() {}

  .
  .
  .

  startUpload() {  
    if (this.selectedFile.type.split('/')[0] !== 'image') { 
      console.error('unsupported file type :( ')
      return;
    } else {
      const path = this.uploadFolder + "/" + this.userID;
      // The main task
      this.task = this.storage.upload(path, this.selectedFile)
      // Progress monitoring
      this.percentage = this.task.percentageChanges();
      this.percentage.subscribe(data => {
        // Do something with my progress
      })
      this.snapshot = this.task.snapshotChanges();
      this.snapshot.subscribe(data => {
        // Do something with my progress
      })
      // The file's download URL
      this.task.then(snapshot => {
        console.log("UPLOAD SUCCESS!");
        snapshot.ref.getDownloadURL().then(url => {
          console.log(url);
          //Do something with my new file's url
        })
      }, 
      (err) => {
        //Do something about errors...  
      });
    }
  }
}

并且我从environment.ts文件中删除了Firebase初始化,因此很明显Angular正在为我在某个地方初始化firebase,因为这是不必要的。我认为这就是为什么Firestore显示我已通过身份验证的原因(因为我已初始化用于登录的Firebase和通过AngularFire进行的Firestore)之间的差异,但是Firebase Storage显示我未通过身份验证(因为我在{{ 1}}文件,这是我不应该使用AngularFireStorage的解决方法)。因此,基本上所有这些都归结为我不完全不了解Angular在为我做什么的事实。嗯...我真的希望这能在某个时候对某人有所帮助...

答案 2 :(得分:0)

使用Angular应用程序时:

const storage = firebase.storage();

它初始化[DEFAULTE]应用程序的存储,该应用程序的存储请求中肯定不包含授权数据(已记录的用户)。 它可以不使用AngularFireStorage来解决(对我来说,对listAll()不支持的backet path ref使用AngularFireStorage方法是必需的)。 有一种变通方法,如何使用包含授权用户数据的正确应用程序初始化火灾存储:

    const apps = firebase.apps;         
    const app = apps && apps.find(x => x.name != '[DEFAULT]');
    const storage = firebase.storage(app);

以上所有内容表示您已经更新了存储规则

allow read, write: if request.auth != null;

答案 3 :(得分:0)

就我而言,我遇到了同样的错误,因为我试图用较新的版本替换/覆盖现有文件,但使用现有文件“密钥”。解决办法是先删除旧文件,再上传新版本。

onUploadImageBtnClicked(event, item) {
    this.afStorage.storage.ref('images/' + item.key).getDownloadURL()
        .then( url => {

            this.replaceExistingImage(event, item);

        }).catch( err => {

            if (err.code === 'storage/object-not-found') {

                this.uploadImage(event, item);

            }

        });
}

replaceExistingImage(event, item) {
    const sub = this.afStorage.ref('images/' + item.key)
        .delete().subscribe( () => {

            sub.unsubscribe();
            this.uploadImage(event, item);

        });
}

uploadImage(event, item) {
    const files = event.target.files;
    let fileRef;

    if (this.uploadImageSubscription) {
        this.uploadImageSubscription.unsubscribe();
    }

    this.uploadImageSubscription = this.afStorage.upload('images/' + item.key, files[0])
        .snapshotChanges()
        .pipe(
            finalize(() => {
                fileRef.ref.getDownloadURL().then( url => {

                    this.db.database.ref('items/' + item.key)
                        .update({
                            imageUrl: url

                        });

                });
            })
        ).subscribe( file => { fileRef = file; });
}