用户已通过身份验证,但是:出现 "Firebase Storage: User does not have permission to access 'path'" 错误
User is AUTHENTICATED, but: "Firebase Storage: User does not have permission to access 'path'" error coming up
我已经在我的应用程序中实现了身份验证,并且在创建用户和身份验证方面没有问题。但是,现在我正在尝试将文件上传到 Firebase 存储,但只有当我删除身份验证规则并进行访问 public 时,它才会起作用。如果我保留默认规则只允许经过身份验证的用户访问(这是我想要的),我会收到错误消息:Firebase Storage: User does not have permission to access 'profile-image/test.PNG'.
我在发出请求之前调用了一个方法来验证我的身份验证状态,我可以 read/write 到 firestore 数据库没有问题,所以我确定我已经通过身份验证。
我是一个彻头彻尾的 FNG,所以这个问题很可能是我 done/not 做的一些愚蠢的事情。如果相关,我正在使用 Angular。我还使用 Google Cloud Platform 激活了一个结算帐户,但这并没有什么不同。
这是我的控制台日志,显示了我使用的引用、我尝试添加的文件(同样,当我访问 public 时,这两个都工作得很好)、我在 auth 状态下的 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);
之前,身份验证对我来说工作得很好,但是当我尝试发出上传请求时,我在没有这一行的情况下遇到了错误。
非常感谢您提出任何建议,如果我需要提供更多信息,请告诉我!
allow read, write: if request.auth != null;
意味着,您必须先登录才能在那里写信。
只需使用 ==
尝试一次,您就会发现它有效。
事实证明,我使用 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 显示我已通过身份验证之间存在差异的原因(因为我通过 AngularFire 初始化了用于登录的 firebase 和 firestore),但 Firebase Storage 显示我未通过身份验证(因为我为此初始化了 firebase单独在我的 environment.ts
文件中,这是我没有像我应该的那样使用 AngularFireStorage 的解决方法)。所以基本上这一切都归结为我没有(不)完全理解 Angular 到底为我做了什么。呃...我真的希望这在某些时候对某人有所帮助...
对于Angular应用,当你使用:
const storage = firebase.storage();
它为 [DEFAULTE] 应用程序初始化存储,当然不包括存储请求的授权数据(已登录的用户)。
它可以在不使用 AngularFireStorage
的情况下解决(对我来说,需要对 AngularFireStorage
不支持的 backet 路径引用使用 listAll()
方法)。
解决方法是如何使用包含授权用户数据的正确应用程序初始化消防存储:
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;
在我的例子中,我遇到了同样的错误,因为我试图 replace/override 具有较新版本但使用现有文件“密钥”的现有文件。解决方案是先删除旧文件,再上传新版本。
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; });
}
我已经在我的应用程序中实现了身份验证,并且在创建用户和身份验证方面没有问题。但是,现在我正在尝试将文件上传到 Firebase 存储,但只有当我删除身份验证规则并进行访问 public 时,它才会起作用。如果我保留默认规则只允许经过身份验证的用户访问(这是我想要的),我会收到错误消息:Firebase Storage: User does not have permission to access 'profile-image/test.PNG'.
我在发出请求之前调用了一个方法来验证我的身份验证状态,我可以 read/write 到 firestore 数据库没有问题,所以我确定我已经通过身份验证。
我是一个彻头彻尾的 FNG,所以这个问题很可能是我 done/not 做的一些愚蠢的事情。如果相关,我正在使用 Angular。我还使用 Google Cloud Platform 激活了一个结算帐户,但这并没有什么不同。
这是我的控制台日志,显示了我使用的引用、我尝试添加的文件(同样,当我访问 public 时,这两个都工作得很好)、我在 auth 状态下的 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);
之前,身份验证对我来说工作得很好,但是当我尝试发出上传请求时,我在没有这一行的情况下遇到了错误。
非常感谢您提出任何建议,如果我需要提供更多信息,请告诉我!
allow read, write: if request.auth != null;
意味着,您必须先登录才能在那里写信。
只需使用 ==
尝试一次,您就会发现它有效。
事实证明,我使用 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 显示我已通过身份验证之间存在差异的原因(因为我通过 AngularFire 初始化了用于登录的 firebase 和 firestore),但 Firebase Storage 显示我未通过身份验证(因为我为此初始化了 firebase单独在我的 environment.ts
文件中,这是我没有像我应该的那样使用 AngularFireStorage 的解决方法)。所以基本上这一切都归结为我没有(不)完全理解 Angular 到底为我做了什么。呃...我真的希望这在某些时候对某人有所帮助...
对于Angular应用,当你使用:
const storage = firebase.storage();
它为 [DEFAULTE] 应用程序初始化存储,当然不包括存储请求的授权数据(已登录的用户)。
它可以在不使用 AngularFireStorage
的情况下解决(对我来说,需要对 AngularFireStorage
不支持的 backet 路径引用使用 listAll()
方法)。
解决方法是如何使用包含授权用户数据的正确应用程序初始化消防存储:
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;
在我的例子中,我遇到了同样的错误,因为我试图 replace/override 具有较新版本但使用现有文件“密钥”的现有文件。解决方案是先删除旧文件,再上传新版本。
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; });
}