如何在Vite+React项目中基于Firebase实现谷歌登录的角色权限控制
解决方案:Vite + React + Firebase Auth 实现RBAC角色分配与权限控制
一、谷歌登录时的角色分配策略
针对你的场景,推荐以下几种实用的角色分配方案,可根据业务需求选择:
- 预注册白名单机制:提前将教师/管理员的谷歌邮箱存入Firebase Firestore(或实时数据库)的白名单集合中。用户首次通过谷歌登录后,系统自动比对其邮箱是否在对应白名单内,匹配则分配对应角色,未匹配则默认分配学生角色。这种方式适合角色明确、无需用户自主选择的场景。
- 首次登录角色引导:新用户首次登录后,弹出角色选择界面(仅提供教师/学生选项,管理员角色需由现有管理员手动分配),用户选择后将角色信息存入数据库。这种方式适合允许用户自主申请教师身份的场景,后续可由管理员审核调整。
- 管理员手动分配:所有新用户默认分配学生角色,管理员通过后台管理界面,手动将指定用户的角色修改为教师或管理员。这种方式权限管控最严格,适合对角色真实性要求高的场景。
二、代码实现思路与核心代码片段
1. 基础配置
确保已在项目中集成Firebase Auth和Firestore:
// firebaseConfig.js import { initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; const firebaseConfig = { // 你的Firebase配置信息 }; const app = initializeApp(firebaseConfig); export const auth = getAuth(app); export const db = getFirestore(app);
2. 谷歌登录与角色分配逻辑
登录成功后,检查用户是否已有角色,无则分配:
// AuthService.js import { signInWithPopup, GoogleAuthProvider } from "firebase/auth"; import { auth, db } from "./firebaseConfig"; import { doc, getDoc, setDoc } from "firebase/firestore"; const provider = new GoogleAuthProvider(); export const signInWithGoogle = async () => { try { const result = await signInWithPopup(auth, provider); const user = result.user; // 检查Firestore中是否存在该用户的角色记录 const userDocRef = doc(db, "users", user.uid); const userDoc = await getDoc(userDocRef); if (!userDoc.exists()) { // 方案1:使用预注册白名单分配角色 const teacherWhitelist = ["teacher1@school.com", "teacher2@school.com"]; // 可从Firestore白名单集合读取 const adminWhitelist = ["admin@school.com"]; let role = "student"; if (adminWhitelist.includes(user.email)) { role = "admin"; } else if (teacherWhitelist.includes(user.email)) { role = "teacher"; } // 将角色存入Firestore用户文档 await setDoc(userDocRef, { uid: user.uid, email: user.email, displayName: user.displayName, role: role, createdAt: new Date() }); // 调用云函数设置Firebase Custom Claims(用于后端/安全规则验证) await fetch("/api/setCustomClaims", { method: "POST", body: JSON.stringify({ uid: user.uid, role: role }) }); } return user; } catch (error) { console.error("登录失败:", error); throw error; } };
3. 云函数设置Custom Claims(关键)
由于前端无法直接设置Firebase Custom Claims,需通过Firebase云函数实现:
// functions/index.js const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.setCustomClaims = functions.https.onCall(async (data, context) => { // 仅允许管理员调用此函数(可选,增强安全性) if (context.auth?.token.role !== "admin") { throw new functions.https.HttpsError("permission-denied", "无权限执行此操作"); } const { uid, role } = data; await admin.auth().setCustomUserClaims(uid, { role }); return { message: `角色${role}已分配给用户${uid}` }; });
4. 前端权限控制(路由守卫)
使用React Router实现路由级别的权限控制:
// ProtectedRoute.jsx import { Navigate, Outlet } from "react-router-dom"; import { useAuthState } from "react-firebase-hooks/auth"; import { auth, db } from "./firebaseConfig"; import { doc, getDoc } from "firebase/firestore"; import { useState, useEffect } from "react"; export const ProtectedRoute = ({ requiredRole }) => { const [user, loading] = useAuthState(auth); const [userRole, setUserRole] = useState(null); useEffect(() => { const fetchUserRole = async () => { if (user) { const userDoc = await getDoc(doc(db, "users", user.uid)); if (userDoc.exists()) { setUserRole(userDoc.data().role); } } }; fetchUserRole(); }, [user]); if (loading) return <div>加载中...</div>; if (!user) return <Navigate to="/login" />; // 检查角色是否匹配 if (requiredRole && userRole !== requiredRole) { return <Navigate to="/unauthorized" />; } return <Outlet />; };
5. 组件级权限控制
在组件中根据角色隐藏/显示内容:
// Dashboard.jsx import { useAuthState } from "react-firebase-hooks/auth"; import { auth, db } from "./firebaseConfig"; import { doc, getDoc } from "firebase/firestore"; import { useState, useEffect } from "react"; export const Dashboard = () => { const [user] = useAuthState(auth); const [userRole, setUserRole] = useState(null); useEffect(() => { const fetchRole = async () => { const docSnap = await getDoc(doc(db, "users", user.uid)); setUserRole(docSnap.data().role); }; if (user) fetchRole(); }, [user]); return ( <div> <h1>控制台</h1> {userRole === "admin" && <button>管理网站设置</button>} {userRole === "teacher" && <button>管理学生信息</button>} {userRole === "student" && <p>欢迎查看你的学习内容</p>} </div> ); };
内容的提问来源于stack exchange,提问作者HEMANT RAJ
相关产品推荐
相关产品推荐

