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

NextJS SSG站点:Firebase免费计划下仅用Client SDK实现RBAC?

仅用Firebase Client SDK实现RBAC的解决方案(适配SSG站点与免费计划)

核心思路:用Firestore存储用户角色

由于无法使用Admin SDK或Cloud Functions,最可靠的方式是将用户角色存储在Firestore中,结合Firestore安全规则实现权限控制,同时在前端通过Client SDK读取角色并渲染对应UI。

步骤1:设计Firestore角色存储结构

创建两个集合(按需合并或拆分):

  • users:存储用户基础信息(登录时自动创建,方便管理员查看用户列表)
  • user_roles:存储用户角色,文档ID为用户UID,包含role字段(如admin/user)

步骤2:初始化管理员角色

手动在Firebase控制台创建第一个管理员的角色文档:

  1. 打开Firestore控制台,创建user_roles集合
  2. 添加文档,ID设为你的Google账号UID(可在Auth控制台查看)
  3. 添加字段role,值设为admin

步骤3:前端实现角色获取与UI控制

在Next.js客户端组件中,监听用户登录状态,从Firestore读取角色并渲染对应UI(SSG预渲染无法获取用户信息,所有权限相关逻辑需放在客户端组件):

'use client';

import { useState, useEffect } from 'react';
import { getAuth, onAuthStateChanged, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
import { getFirestore, doc, getDoc, setDoc } from 'firebase/firestore';

const auth = getAuth();
const db = getFirestore();
const provider = new GoogleAuthProvider();

export default function AuthAndRoleDemo() {
  const [user, setUser] = useState(null);
  const [role, setRole] = useState(null);

  // 处理Google登录
  const handleGoogleLogin = async () => {
    try {
      const userCredential = await signInWithPopup(auth, provider);
      const loggedUser = userCredential.user;
      // 自动创建用户基础文档(如果不存在)
      const userDocRef = doc(db, 'users', loggedUser.uid);
      const userDoc = await getDoc(userDocRef);
      if (!userDoc.exists()) {
        await setDoc(userDocRef, {
          uid: loggedUser.uid,
          email: loggedUser.email,
          displayName: loggedUser.displayName,
          createdAt: new Date()
        });
        // 默认分配普通用户角色
        await setDoc(doc(db, 'user_roles', loggedUser.uid), { role: 'user' });
      }
    } catch (error) {
      console.error('登录失败:', error);
    }
  };

  // 监听用户状态变化,获取角色
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
      if (currentUser) {
        setUser(currentUser);
        const roleDoc = await getDoc(doc(db, 'user_roles', currentUser.uid));
        setRole(roleDoc.exists() ? roleDoc.data().role : 'user');
      } else {
        setUser(null);
        setRole(null);
      }
    });
    return unsubscribe;
  }, [auth, db]);

  if (!user) {
    return <button onClick={handleGoogleLogin}>用Google登录</button>;
  }

  return (
    <div>
      <h3>欢迎,{user.displayName}</h3>
      <p>当前角色:{role}</p>
      {/* 仅管理员可见的功能 */}
      {role === 'admin' && (
        <div>
          <h4>管理员面板</h4>
          <button>管理用户角色</button>
        </div>
      )}
      {/* 普通用户功能 */}
      {role === 'user' && <p>普通用户专属内容</p>}
    </div>
  );
}

步骤4:配置Firestore安全规则

通过规则确保角色的安全性(只有管理员能修改角色,用户只能查看自己的角色,其他资源按角色控制权限):

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 用户角色文档规则
    match /user_roles/{uid} {
      // 用户只能读取自己的角色
      allow read: if request.auth != null && request.auth.uid == uid;
      // 仅管理员能修改所有角色
      allow write: if request.auth != null && get(/databases/$(database)/documents/user_roles/$(request.auth.uid)).data.role == 'admin';
    }

    // 用户基础信息文档规则
    match /users/{uid} {
      allow read: if request.auth != null && (
        request.auth.uid == uid || 
        get(/databases/$(database)/documents/user_roles/$(request.auth.uid)).data.role == 'admin'
      );
      allow write: if request.auth != null && request.auth.uid == uid;
    }

    // 示例:内容集合权限控制
    match /content/{docId} {
      // 所有登录用户可读取
      allow read: if request.auth != null;
      // 管理员或内容作者可修改
      allow write: if request.auth != null && (
        get(/databases/$(database)/documents/user_roles/$(request.auth.uid)).data.role == 'admin' ||
        resource.data.authorId == request.auth.uid
      );
    }
  }
}

步骤5:实现管理员分配角色功能

管理员可通过UI查询users集合获取所有用户,然后修改对应user_roles文档的角色值(Client SDK直接调用setDoc或updateDoc即可,Firestore规则会自动验证权限):

// 管理员修改用户角色的函数
const updateUserRole = async (targetUid, newRole) => {
  try {
    await setDoc(doc(db, 'user_roles', targetUid), { role: newRole }, { merge: true });
    alert('角色修改成功');
  } catch (error) {
    console.error('修改角色失败:', error);
  }
};

方案优缺点

  • 优点:完全依赖Firebase Client SDK,无需服务器或Cloud Functions,适配Spark免费计划;SSG站点可通过客户端组件正常处理权限逻辑;角色存储在Firestore,安全规则确保权限不可篡改。
  • 缺点:每次获取角色需读取Firestore(可通过localStorage缓存角色信息减少请求);Client SDK无法直接列出所有Auth用户,需依赖users集合维护用户列表。

内容的提问来源于stack exchange,提问作者FAQi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:00:33