将图像上传到 FirebaseStorage - 权限被拒绝
Uploading Image to FirebaseStorage - Permission Denied
我正在 authentication/signing 开发 FirebaseStorage,用户可以在注册期间上传图片,但我一直收到关于 用户没有任何权限的错误。 当用户点击注册时,使用 Firebase 的注册过程会起作用,因为用户是在身份验证中创建的,但信息不会保存在 Firestore 数据库中。有谁知道我做错了什么?
这是我的 Firebase 存储规则:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /user-images {
allow read, create: if request.auth != null;
}
match /avatar.png {
allow read: if true;
}
}
}
这是注册屏幕。
import 'dart:io';
import 'dart:ui';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/services.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '/screens/auth_screen.dart';
import '/widgets/build_row.dart';
import '/screens/chat_screen.dart';
import '/widgets/user_image_picker.dart';
class SignUpScreen extends StatefulWidget {
static const routeName = '/sign-up-screen';
@override
_SignUpScreenState createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
final _auth = FirebaseAuth.instance;
final GlobalKey<FormState> _key = GlobalKey();
String _username = '';
String _userPW = '';
String _userEmail = '';
var _isLoading = false;
File? _userImageFile;
void _pickedImage(File image) {
_userImageFile = image;
}
void _submit() {
final isValid = _key.currentState!.validate();
if (isValid) {
_key.currentState!.save();
}
this._submitAuthForm(_userEmail.trim(), _userPW.trim(), _username.trim());
}
void _submitAuthForm(
String userEmail,
String password,
String username,
) async {
try {
setState(() {
_isLoading = true;
});
UserCredential userCredential = await _auth
.createUserWithEmailAndPassword(email: userEmail, password: password);
final ref = FirebaseStorage.instance
.ref()
.child('user-images/${userCredential.user!.uid}.jpg');
final avatarRef = FirebaseStorage.instance.ref().child('avatar.png');
var url;
if (_userImageFile == null) {
url = await avatarRef.getDownloadURL();
}
if (_userImageFile != null) {
await ref.putFile(_userImageFile!);
url = await ref.getDownloadURL();
}
await FirebaseFirestore.instance
.collection('users')
.doc(userCredential.user!.uid)
.set(
{
'username': username,
'email': userEmail,
'image_url': url,
},
);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (ctx) => ChatScreen()),
);
} on PlatformException catch (error) {
var message = 'An Error has occured!';
if (error.message != null) {
message = error.message!;
}
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.deepPurple.shade600,
),
);
} catch (error) {
if (error.toString().contains(
'The email address is already in use by another account.')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('The email address is already in use by another account.'),
backgroundColor: Colors.deepPurple.shade600,
),
);
}
print(error);
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: deviceSize.width * 0.8,
constraints: BoxConstraints(
maxHeight: (deviceSize.height -
MediaQuery.of(context).viewInsets.bottom) *
0.7),
decoration: BoxDecoration(
border: Border.all(
color: Colors.deepPurple.shade600,
width: 2.0,
),
color: Colors.white,
),
child: SingleChildScrollView(
padding: EdgeInsets.only(
left: 10,
bottom: 20,
),
child: Form(
key: _key,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
UserImagePicker(_pickedImage),
BuildRowWithIcon(Icons.person, 'Username:', false,
(value) {
if (value.isEmpty) {
return 'Please enter a username';
}
return null;
}, (value) {
_username = value;
}, 'Username must be at least 6 characters long. Special characters: $, ^, &, !, ?'),
BuildRow(
Icons.email_outlined,
'E-mail:',
false,
(value) {
if (value.isEmpty) {
return 'Please provide an e-mail';
}
return null;
},
(value) {
_userEmail = value;
},
),
BuildRowWithIcon(
Icons.password_outlined, 'Password:', true,
(value) {
if (value.isEmpty) {
return 'Please enter a password';
}
if (value.length < 7) {
return 'Password must be at least 7 characters long';
}
return null;
}, (value) {
_userPW = value;
}, 'Password must be at least 7 characters long. You must use a combination of two of the following: letters, numbers, & special characters. Special characters: $, ^, &, !, ?'),
BuildRowWithIcon(
Icons.password_outlined,
'Confirm PW:',
true,
(value) {
if (value.isEmpty) {
return 'Please enter the same password';
}
return null;
},
null,
'Confirm password',
),
],
),
),
),
),
const SizedBox(height: 30),
if (_isLoading) CircularProgressIndicator(),
if (!_isLoading)
ElevatedButton(
onPressed: _submit,
child: const Text(
'Sign up!',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
style: ButtonStyle(
elevation: MaterialStateProperty.all<double>(20),
backgroundColor: MaterialStateProperty.all<Color>(
Colors.deepPurple.shade600),
foregroundColor: MaterialStateProperty.all<Color>(
Colors.tealAccent.shade200),
padding: MaterialStateProperty.all<EdgeInsets>(
EdgeInsets.all(15)),
shadowColor: MaterialStateProperty.all<Color>(
Colors.deepPurple.shade800),
shape: MaterialStateProperty.all<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
),
),
SizedBox(height: 25),
TextButton(
onPressed: () => Navigator.of(context)
.pushReplacementNamed(AuthScreen.routeName),
child: Text(
'I already have an account.',
style: TextStyle(
color: Colors.deepPurple.shade400,
fontSize: 15,
),
),
),
],
),
),
),
);
}
}
这是 ImagePicker() 小部件
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as syspath;
class UserImagePicker extends StatefulWidget {
final void Function(File pickedImage) imagePickFn;
UserImagePicker(this.imagePickFn);
@override
_UserImagePickerState createState() => _UserImagePickerState();
}
class _UserImagePickerState extends State<UserImagePicker> {
File? _pickedImage;
Future<void> _pickImage() async {
final _pickedImageXFile =
await ImagePicker().pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxHeight: 150,
maxWidth: 150);
if (_pickedImageXFile == null) {
return;
}
final _pickedImageFile = File(_pickedImageXFile.path);
setState(() {
_pickedImage = _pickedImageFile;
});
final appDir = await syspath.getApplicationDocumentsDirectory();
final fileName = path.basename(_pickedImageFile.path);
final userImageFile =
await _pickedImageFile.copy('${appDir.path}/$fileName');
widget.imagePickFn(userImageFile);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 10),
child: CircleAvatar(
radius: 35,
backgroundColor: Colors.tealAccent.shade200,
backgroundImage:
_pickedImage == null ? null : FileImage(_pickedImage!),
),
),
TextButton.icon(
onPressed: _pickImage,
icon: Icon(
Icons.image_outlined,
color: Colors.deepPurple.shade600,
),
label: Text(
'Add User Image',
style: TextStyle(color: Colors.deepPurple.shade600),
),
),
],
);
}
}
附带问题:
有没有办法使用 Future 进行验证?我希望能够在 he/she 输入 password/confirm 密码时向用户显示它不符合要求或确认密码值不是密码值。我需要为此使用单独的 TextController 吗?
您的规则中的 match /user-images
匹配根名称 user-images
中的文件。它不会递归地将权限应用到过滤器中的文件。
要允许任何人在 /user-images
下写入文件,您可以应用 wildcard match:
match /user-images/{file} {
allow read, create: if request.auth != null;
}
或者如果您想允许在 /user-images
下的任何文件夹中写入:
match /user-images/{file=**} {
allow read, create: if request.auth != null;
}
然后让大家阅读/user-images/avatar.png
:
match /user-images/avatar.png {
allow read: if true;
}
您需要检查规则部分的权限并允许读取、写入:如果为真;
并检查 AndroidManifest.xml 文件,是否已授予所需权限。
我正在 authentication/signing 开发 FirebaseStorage,用户可以在注册期间上传图片,但我一直收到关于 用户没有任何权限的错误。 当用户点击注册时,使用 Firebase 的注册过程会起作用,因为用户是在身份验证中创建的,但信息不会保存在 Firestore 数据库中。有谁知道我做错了什么?
这是我的 Firebase 存储规则:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /user-images {
allow read, create: if request.auth != null;
}
match /avatar.png {
allow read: if true;
}
}
}
这是注册屏幕。
import 'dart:io';
import 'dart:ui';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/services.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '/screens/auth_screen.dart';
import '/widgets/build_row.dart';
import '/screens/chat_screen.dart';
import '/widgets/user_image_picker.dart';
class SignUpScreen extends StatefulWidget {
static const routeName = '/sign-up-screen';
@override
_SignUpScreenState createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
final _auth = FirebaseAuth.instance;
final GlobalKey<FormState> _key = GlobalKey();
String _username = '';
String _userPW = '';
String _userEmail = '';
var _isLoading = false;
File? _userImageFile;
void _pickedImage(File image) {
_userImageFile = image;
}
void _submit() {
final isValid = _key.currentState!.validate();
if (isValid) {
_key.currentState!.save();
}
this._submitAuthForm(_userEmail.trim(), _userPW.trim(), _username.trim());
}
void _submitAuthForm(
String userEmail,
String password,
String username,
) async {
try {
setState(() {
_isLoading = true;
});
UserCredential userCredential = await _auth
.createUserWithEmailAndPassword(email: userEmail, password: password);
final ref = FirebaseStorage.instance
.ref()
.child('user-images/${userCredential.user!.uid}.jpg');
final avatarRef = FirebaseStorage.instance.ref().child('avatar.png');
var url;
if (_userImageFile == null) {
url = await avatarRef.getDownloadURL();
}
if (_userImageFile != null) {
await ref.putFile(_userImageFile!);
url = await ref.getDownloadURL();
}
await FirebaseFirestore.instance
.collection('users')
.doc(userCredential.user!.uid)
.set(
{
'username': username,
'email': userEmail,
'image_url': url,
},
);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (ctx) => ChatScreen()),
);
} on PlatformException catch (error) {
var message = 'An Error has occured!';
if (error.message != null) {
message = error.message!;
}
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.deepPurple.shade600,
),
);
} catch (error) {
if (error.toString().contains(
'The email address is already in use by another account.')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('The email address is already in use by another account.'),
backgroundColor: Colors.deepPurple.shade600,
),
);
}
print(error);
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: deviceSize.width * 0.8,
constraints: BoxConstraints(
maxHeight: (deviceSize.height -
MediaQuery.of(context).viewInsets.bottom) *
0.7),
decoration: BoxDecoration(
border: Border.all(
color: Colors.deepPurple.shade600,
width: 2.0,
),
color: Colors.white,
),
child: SingleChildScrollView(
padding: EdgeInsets.only(
left: 10,
bottom: 20,
),
child: Form(
key: _key,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
UserImagePicker(_pickedImage),
BuildRowWithIcon(Icons.person, 'Username:', false,
(value) {
if (value.isEmpty) {
return 'Please enter a username';
}
return null;
}, (value) {
_username = value;
}, 'Username must be at least 6 characters long. Special characters: $, ^, &, !, ?'),
BuildRow(
Icons.email_outlined,
'E-mail:',
false,
(value) {
if (value.isEmpty) {
return 'Please provide an e-mail';
}
return null;
},
(value) {
_userEmail = value;
},
),
BuildRowWithIcon(
Icons.password_outlined, 'Password:', true,
(value) {
if (value.isEmpty) {
return 'Please enter a password';
}
if (value.length < 7) {
return 'Password must be at least 7 characters long';
}
return null;
}, (value) {
_userPW = value;
}, 'Password must be at least 7 characters long. You must use a combination of two of the following: letters, numbers, & special characters. Special characters: $, ^, &, !, ?'),
BuildRowWithIcon(
Icons.password_outlined,
'Confirm PW:',
true,
(value) {
if (value.isEmpty) {
return 'Please enter the same password';
}
return null;
},
null,
'Confirm password',
),
],
),
),
),
),
const SizedBox(height: 30),
if (_isLoading) CircularProgressIndicator(),
if (!_isLoading)
ElevatedButton(
onPressed: _submit,
child: const Text(
'Sign up!',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
style: ButtonStyle(
elevation: MaterialStateProperty.all<double>(20),
backgroundColor: MaterialStateProperty.all<Color>(
Colors.deepPurple.shade600),
foregroundColor: MaterialStateProperty.all<Color>(
Colors.tealAccent.shade200),
padding: MaterialStateProperty.all<EdgeInsets>(
EdgeInsets.all(15)),
shadowColor: MaterialStateProperty.all<Color>(
Colors.deepPurple.shade800),
shape: MaterialStateProperty.all<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
),
),
SizedBox(height: 25),
TextButton(
onPressed: () => Navigator.of(context)
.pushReplacementNamed(AuthScreen.routeName),
child: Text(
'I already have an account.',
style: TextStyle(
color: Colors.deepPurple.shade400,
fontSize: 15,
),
),
),
],
),
),
),
);
}
}
这是 ImagePicker() 小部件
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as syspath;
class UserImagePicker extends StatefulWidget {
final void Function(File pickedImage) imagePickFn;
UserImagePicker(this.imagePickFn);
@override
_UserImagePickerState createState() => _UserImagePickerState();
}
class _UserImagePickerState extends State<UserImagePicker> {
File? _pickedImage;
Future<void> _pickImage() async {
final _pickedImageXFile =
await ImagePicker().pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxHeight: 150,
maxWidth: 150);
if (_pickedImageXFile == null) {
return;
}
final _pickedImageFile = File(_pickedImageXFile.path);
setState(() {
_pickedImage = _pickedImageFile;
});
final appDir = await syspath.getApplicationDocumentsDirectory();
final fileName = path.basename(_pickedImageFile.path);
final userImageFile =
await _pickedImageFile.copy('${appDir.path}/$fileName');
widget.imagePickFn(userImageFile);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 10),
child: CircleAvatar(
radius: 35,
backgroundColor: Colors.tealAccent.shade200,
backgroundImage:
_pickedImage == null ? null : FileImage(_pickedImage!),
),
),
TextButton.icon(
onPressed: _pickImage,
icon: Icon(
Icons.image_outlined,
color: Colors.deepPurple.shade600,
),
label: Text(
'Add User Image',
style: TextStyle(color: Colors.deepPurple.shade600),
),
),
],
);
}
}
附带问题: 有没有办法使用 Future 进行验证?我希望能够在 he/she 输入 password/confirm 密码时向用户显示它不符合要求或确认密码值不是密码值。我需要为此使用单独的 TextController 吗?
您的规则中的 match /user-images
匹配根名称 user-images
中的文件。它不会递归地将权限应用到过滤器中的文件。
要允许任何人在 /user-images
下写入文件,您可以应用 wildcard match:
match /user-images/{file} {
allow read, create: if request.auth != null;
}
或者如果您想允许在 /user-images
下的任何文件夹中写入:
match /user-images/{file=**} {
allow read, create: if request.auth != null;
}
然后让大家阅读/user-images/avatar.png
:
match /user-images/avatar.png {
allow read: if true;
}
您需要检查规则部分的权限并允许读取、写入:如果为真;
并检查 AndroidManifest.xml 文件,是否已授予所需权限。