Firebase Realtime Database匿名认证后读取数据返回null问题
我是初级开发,需求是用户打开首页自动完成匿名身份认证,认证成功后从Realtime Database拉取数据,仅允许用户读取和自身uid匹配路径下的数据。
当前配置的数据库安全规则:
{ "rules": { "$uid": { ".read": "auth != null && auth.uid == $uid", ".write": "false", } } }
数据库结构参考对应截图。
全局AuthContext中的匿名登录逻辑:
export const AuthContextProvider = ({ children }: Props) => { React.useEffect(() => { signInAnonymously(auth) .then(() => { // 登录完成逻辑 }) .catch((error) => { const errorCode = error.code; const errorMessage = error.message; // 错误处理 }); }, []); const values = {}; return <AuthContext.Provider value={values}>{children}</AuthContext.Provider>; };
访问/crew页面时,getStaticProps会触发fetchPlanetsInfo方法:
export const getStaticProps: GetStaticProps = async () => { const planetInfo = await fetchPlanetsInfo("destinations"); return { props: { data: planetInfo, }, }; };
export function fetchPlanetsInfo(query: string) { let dataArr: any = []; try { onAuthStateChanged(auth, (user) => { if (user) { const uid = user.uid; const databaseCall = ref(db, uid + "/" + query); onValue(databaseCall, (snapshot) => { const data = snapshot.val(); dataArr.push(data); }); } else { console.log("user is signout"); // 未登录处理 } }); } catch (error) { console.log(error); } return dataArr.flat(); }
故障表现:无法从数据库获取数据,fetchPlanetsInfo返回null,初步怀疑是安全规则配置问题。
你的安全规则权限判断逻辑本身没有问题,故障是三个错误叠加导致的:
安全规则存在语法错误
你写的规则中.write: "false"末尾多了一个多余的逗号,Realtime Database规则采用类JSON语法,不允许键值对后存在尾逗号,会直接导致规则解析失败,所有数据库请求默认被拒绝。先删除这个逗号再重新发布规则。数据请求位置错误
Next.js的getStaticProps是项目构建阶段在服务端执行的代码,根本不会在用户浏览器端运行:- 执行时拿不到浏览器端的匿名登录状态
- 你写在
AuthContext里的匿名登录逻辑是客户端侧的,构建阶段根本不会执行 - 服务端环境下的
onAuthStateChanged无法监听到客户端的用户登录态,自然拿不到对应uid
拉取用户私有数据不能放在getStaticProps中,改成在客户端页面组件内,确认拿到用户登录态后再发起请求;如果要做服务端渲染,改用getServerSideProps,配合客户端传递的id token完成服务端认证后再查库。
异步逻辑写法错误
onAuthStateChanged和onValue都是异步订阅监听器,你当前的写法是刚注册完监听器就立刻执行return dataArr.flat(),此时异步回调还没触发,dataArr是空数组,根本等不到数据返回。
正确写法需要返回Promise,等拿到数据后再返回结果,参考如下改法:export async function fetchPlanetsInfo(query: string) { // 等待获取当前登录用户 const user = await new Promise((resolve, reject) => { const unsubscribe = onAuthStateChanged(auth, (currentUser) => { unsubscribe(); // 拿到一次用户状态就取消订阅,避免内存泄漏 resolve(currentUser); }, reject); }); if (!user) throw new Error("用户未登录"); // 读取对应路径数据 const dbRef = ref(db, `${user.uid}/${query}`); const snapshot = await get(dbRef); // 单次读取用get方法,不需要持续订阅的场景不要用onValue return snapshot.val(); }补充:如果只需要读取一次数据,不要用
onValue,它是用来持续监听数据实时变化的,单次读取用get()即可,避免不必要的内存泄漏。
全部改完后,先手动触发匿名登录确认拿到uid,再核对数据库中对应用户uid路径下确实存在destinations节点,避免路径拼写错误。
内容的提问来源于stack exchange,提问作者Sandrew94

