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控制台创建第一个管理员的角色文档:
- 打开Firestore控制台,创建
user_roles集合 - 添加文档,ID设为你的Google账号UID(可在Auth控制台查看)
- 添加字段
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
相关产品推荐
相关产品推荐

