React上传图片至Firebase Storage权限问题解决方案咨询
问题:Firebase Storage上传头像权限错误
上传头像时持续收到错误:
FirebaseError: Firebase Storage: User does not have permission to access 'files/hhh.png'. (storage/unauthorized)
不想放宽Storage安全规则允许所有用户访问,希望通过修改代码解决。
当前Firebase Storage安全规则
// Grants a user access to a node matching their user ID service firebase.storage { match /b/{bucket}/o { // Files look like: "user/<UID>/path/to/file.txt" match /user/{userId}/{allPaths=**} { allow read, write: if request.auth != null && request.auth.uid == userId; } } }
Firebase配置文件
import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; import 'firebase/compat/firestore'; import { getStorage } from "firebase/storage"; const firebaseConfig = { }; const firebaseApp = firebase.initializeApp(firebaseConfig); const db = firebaseApp.firestore(); const auth = firebase.auth(); export const storage = getStorage(firebaseApp); export {db, auth};
Login组件代码
import React, { useState } from "react"; import { auth } from "./firebase"; import { useDispatch } from "react-redux"; import { login } from "./features/userSlice"; import hpic from "./images/screenshot.png"; import { storage } from './firebase'; import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage"; function Login() { const [progresspercent, setProgresspercent] = useState(0); const [name, setName] = useState(""); const [profpic, setProfpic] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const dispatch = useDispatch(); const loginToApp = (e) => { e.preventDefault(); const file = e.target[1]?.files[0] if (!file) return; const storageRef = ref(storage, `files/${file.name}`); const uploadTask = uploadBytesResumable(storageRef, file); uploadTask.on("state_changed", (snapshot) => { const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100); setProgresspercent(progress); }, (error) => { alert(error); }, () => { getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { setProfpic(downloadURL) }); } ); auth .signInWithEmailAndPassword(email, password) .then((userAuth) => { dispatch( login({ email: userAuth.user.email, uid: userAuth.user.uid, displayName: userAuth.user.displayName, photoUrl: userAuth.user.photoURL, }) ); }) .catch((error) => alert(error)); }; const register = () => { if (!name) { return alert("Please enter your name!"); } const storageRef = ref(storage, `files/${profpic.name}`); const uploadTask = uploadBytesResumable(storageRef, profpic); uploadTask.on("state_changed", (snapshot) => { const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100); setProgresspercent(progress); }, (error) => { alert(error); }, () => { getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { auth .createUserWithEmailAndPassword(email, password) .then((userAuth) => { userAuth.user .updateProfile({ displayName: name, photoUrl: downloadURL, }) .then(() => { dispatch( login({ email: userAuth.user.email, uid: userAuth.user.uid, displayName: name, photoUrl: downloadURL, }) ); }); }) .catch((error) => alert(error)); }); } ); }; return ( <div className="grid grid-cols-1 bg-cover bg-center bg-fixed bg-opacity-80 bg-gradient-to-r from-blue-100 to-green-100 md:grid-cols-2 gap-8 mx-auto py-20 px-6 md:px-32 items-center"> <form className="flex flex-col items-center w-full"> <input className="w-[60%] h-12 px-3 rounded mb-3" type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Full name (required if registering)" /> <input className="w-[60%] h-12 px-3 rounded mb-3 " type="file" onChange={(event) => { setProfpic(event.target.files[0]); }} placeholder="Optional Profile Pic (Paste URL Here)" /> <input className="w-[60%] h-12 px-3 rounded mb-3 " type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <input className="w-[60%] h-12 px-3 rounded mb-3 " type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" /> <button className="w-[60%] h-12 bg-green-500 rounded text-white font-bold mb-3 hover:bg-green-500 transition-all duration-200 hover:shadow-xl" type="submit" onClick={loginToApp} > Sign In </button> <p className="bg-white rounded p-2 text-center transition-all duration-200"> Not a member? Fill out the information above and Click: <span className="text-blue-500 hover:text-blue-900 pl-2 font-bold cursor-pointer" onClick={register} > Register Now </span> </p> </form> <div className="hidden md:block"> <img className="w-full h-auto rounded-lg border-2 border-gray-400 object-cover" src={hpic} alt="login pic" /> </div> </div> ); } export default Login
解决方案
问题根源
你的安全规则只允许用户访问/user/{userId}/路径下的文件,但代码中上传路径是files/${file.name},完全不匹配规则要求的路径结构,同时:
- 登录流程:你在用户登录前就发起了文件上传,此时用户还未完成身份验证,
request.auth为null,不符合规则中request.auth != null的要求。 - 注册流程:同样在创建用户前就上传文件,且路径未包含用户UID。
代码修改步骤
1. 调整文件上传路径
按照规则要求,将上传路径改为user/{userId}/{file.name},其中userId是已登录用户的UID。
2. 调整登录流程:先完成登录,再上传文件
修改loginToApp函数,确保用户登录成功后再执行文件上传:
const loginToApp = (e) => { e.preventDefault(); const file = e.target[1]?.files[0]; auth .signInWithEmailAndPassword(email, password) .then((userAuth) => { // 用户登录成功后再处理文件上传 if (file) { const storageRef = ref(storage, `user/${userAuth.user.uid}/${file.name}`); const uploadTask = uploadBytesResumable(storageRef, file); uploadTask.on("state_changed", (snapshot) => { const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100); setProgresspercent(progress); }, (error) => { alert(error); }, () => { getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { // 可选:更新用户头像到Auth userAuth.user.updateProfile({ photoURL: downloadURL }) .then(() => { dispatch(login({ email: userAuth.user.email, uid: userAuth.user.uid, displayName: userAuth.user.displayName, photoUrl: downloadURL, })); }); }); } ); } else { // 无文件时直接分发登录状态 dispatch(login({ email: userAuth.user.email, uid: userAuth.user.uid, displayName: userAuth.user.displayName, photoUrl: userAuth.user.photoURL, })); } }) .catch((error) => alert(error)); };
3. 调整注册流程:先创建用户,再上传文件并更新头像
修改register函数,先创建用户,再用用户UID作为路径上传文件:
const register = () => { if (!name) { return alert("Please enter your name!"); } auth .createUserWithEmailAndPassword(email, password) .then((userAuth) => { if (profpic) { const storageRef = ref(storage, `user/${userAuth.user.uid}/${profpic.name}`); const uploadTask = uploadBytesResumable(storageRef, profpic); uploadTask.on("state_changed", (snapshot) => { const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100); setProgresspercent(progress); }, (error) => { alert(error); }, () => { getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { userAuth.user.updateProfile({ displayName: name, photoURL: downloadURL, }).then(() => { dispatch(login({ email: userAuth.user.email, uid: userAuth.user.uid, displayName: name, photoUrl: downloadURL, })); }); }); } ); } else { // 无头像时直接更新用户名并分发状态 userAuth.user.updateProfile({ displayName: name }) .then(() => { dispatch(login({ email: userAuth.user.email, uid: userAuth.user.uid, displayName: name, photoUrl: userAuth.user.photoURL, })); }); } }) .catch((error) => alert(error)); };
修改后验证
调整后,文件上传路径符合安全规则的/user/{userId}/结构,且只有用户完成身份验证后才会发起上传,此时request.auth不为null且request.auth.uid与路径中的userId一致,完全满足规则要求,无需放宽权限。
内容的提问来源于stack exchange,提问作者Jerm
相关产品推荐
相关产品推荐

