React中Firebase实时数据库权限规则问题及登录状态存储建议
我在React应用中使用Firebase实时数据库与身份验证服务,采用邮箱/密码认证开发库存应用,数据库包含inventory、batches、unfinished三个主节点。登录功能通过signInWithEmailAndPassword实现,因页面频繁刷新,使用sessionStorage存储登录状态。目前存在两个核心问题:
- 设置的数据库规则(
.read和.write为auth.uid != null)未生效,希望仅允许已认证用户读写数据库 - 寻求
sessionStorage存储登录状态的优化建议
登录代码:
const handleSignIn = async (values) => { signInWithEmailAndPassword( auth, values.username, values.password ) .then((userCredential) => { sessionStorage.setItem("user", values.username); sessionStorage.setItem( "login", true ); const user = userCredential.user; history.push("/inv"); setMsg(null); }) .catch((error) => { setMsg("The Email or Password is Invalid"); console.log(error); }); };
数据库规则:
"rules": { ".read": "auth.uid != null", ".write": "auth.uid != null" } }
一、数据库规则不生效的排查与修复
1. 修正规则语法错误
你的规则代码末尾多了一个冗余的},导致规则解析失败直接失效。修正后的正确规则:
{ "rules": { ".read": "auth.uid != null", ".write": "auth.uid != null" } }
修改后需在Firebase控制台的「实时数据库 -> 规则」页面点击发布,确保新规则生效。
2. 同步Firebase Auth客户端状态
数据库规则依赖的是Firebase Auth客户端的身份状态,而非你手动存在sessionStorage的标记。页面刷新后,若未触发Firebase Auth重新验证,数据库请求时auth.uid会为null,导致规则判断失败。
在应用初始化(如App组件或路由守卫)中添加Auth状态监听,自动恢复用户身份:
import { useEffect } from 'react'; import { auth, onAuthStateChanged } from './firebase'; // 在App组件内添加 useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { if (user) { // 同步状态到sessionStorage sessionStorage.setItem("login", "true"); sessionStorage.setItem("user", user.email); } else { // 清空存储并跳转登录页 sessionStorage.removeItem("login"); sessionStorage.removeItem("user"); history.push("/login"); } }); return () => unsubscribe(); // 组件卸载时取消监听 }, [history]);
3. 验证数据库请求前的Auth状态
发起数据库请求前,先确认auth.currentUser是否存在,避免无权限请求:
import { get, ref } from "firebase/database"; import { auth, database } from './firebase'; const fetchInventory = async () => { if (!auth.currentUser) { console.error("用户未认证,无法访问数据库"); return; } const snapshot = await get(ref(database, 'inventory')); // 处理数据逻辑 };
二、sessionStorage存储登录状态的优化建议
1. 减少冗余存储,依赖Firebase Auth原生状态
不需要将用户名(邮箱)存入sessionStorage,直接通过auth.currentUser?.email即可获取。仅保留必要的登录标记,或完全依赖Firebase的Auth状态监听管理登录状态。
2. 用React Context统一管理登录状态
单独使用sessionStorage会导致状态更新无法同步到所有组件,建议创建Auth Context统一管理用户状态:
// AuthContext.js import { createContext, useContext, useState, useEffect } from 'react'; import { auth, onAuthStateChanged } from './firebase'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (currentUser) => { setUser(currentUser); // 辅助存储标记,用于路由快速判断 if (currentUser) { sessionStorage.setItem("isLoggedIn", "true"); } else { sessionStorage.removeItem("isLoggedIn"); } }); return unsubscribe; }, []); return ( <AuthContext.Provider value={{ user }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
组件中直接通过Hook获取状态,无需读取sessionStorage:
const { user } = useAuth(); if (!user) { // 未登录逻辑处理 }
3. 优化路由守卫逻辑
结合Auth Context和sessionStorage做路由拦截,兼顾可靠性与性能:
import { Navigate, useLocation } from 'react-router-dom'; import { useAuth } from './AuthContext'; const PrivateRoute = ({ children }) => { const { user } = useAuth(); const location = useLocation(); // 先通过sessionStorage快速判断,再等待Auth状态确认 if (!user && sessionStorage.getItem("isLoggedIn") !== "true") { return <Navigate to="/login" state={{ from: location }} replace />; } return children; };
4. 注意sessionStorage的局限性
sessionStorage仅在当前标签页有效,关闭标签页后会清空。若需要跨标签页共享登录状态,可改用localStorage,但需确保退出登录时主动清空:
// 退出登录时 const handleSignOut = () => { signOut(auth).then(() => { localStorage.removeItem("isLoggedIn"); history.push("/login"); }); };
内容的提问来源于stack exchange,提问作者Tanishq Jadhav

