You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:34:53