Next.js使用getServerSideProps访问Firebase报insufficient permissions错误
问题原因
你遇到的权限报错本质是getServerSideProps运行在Next.js服务端,而Firebase客户端SDK的登录态仅存在于用户浏览器中,服务端执行Firestore查询时没有携带任何用户身份信息,所以Firebase安全规则判定request.auth为null,触发权限拒绝。
纯React客户端应用所有数据请求都在浏览器执行,会自动携带当前登录的auth状态,所以不会遇到这类问题。
解决方案1:将会话数据获取移到客户端执行(门槛最低,适配现有逻辑)
这种方案不需要修改服务端逻辑,直接把数据请求从getServerSideProps移到页面组件的副作用里,等客户端Firebase auth状态加载完成后再发起请求:
修改你的[conversationid].tsx代码如下:
import React, { useEffect, useState } from "react"; import Head from "next/head"; import Navbar from "../../components/Navbar/Navbar"; import Footer from "../../components/Footer/Footer"; import Chat from "../../components/Chat/Chat"; import { getConversationData } from "../../services/firebase"; // 引入你的Firebase auth实例 import { auth } from "../../services/firebase"; import { onAuthStateChanged } from "firebase/auth"; function Page({ conversationid }) { const [conversationData, setConversationData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isNotFound, setIsNotFound] = useState(false); useEffect(() => { // 等待auth状态加载完成再请求数据 const unsubscribe = onAuthStateChanged(auth, async (user) => { if (!user) { // 未登录可跳转至登录页 window.location.href = '/login'; return; } const response = await getConversationData(conversationid); if (response.success) { setConversationData(response.conversation); } else { setIsNotFound(true); } setIsLoading(false); }); return () => unsubscribe(); }, [conversationid]); if (isLoading) return <div>加载中...</div>; if (isNotFound) return <div>404 会话不存在</div>; return ( <> <Head> <title>Chat</title> <link rel="icon" href="/favicon.ico" /> </Head> <Navbar page={"Profile"} /> <Chat conversation={conversationData} /> <Footer /> </> ); } // 仅通过getServerSideProps传递路由参数即可 export async function getServerSideProps(context) { return { props: { conversationid: context.params.conversationid, }, }; } export default Page;
解决方案2:服务端用Firebase Admin SDK验证身份(适合需要SSR渲染的场景)
如果你需要服务端渲染完整的页面内容,就要用Admin SDK来做服务端的身份校验:
- 首先在客户端登录成功后,把Firebase的ID Token写到cookie中,让服务端可以读取到身份凭证
- 服务端安装Firebase Admin SDK,初始化后读取cookie中的ID Token验证用户身份,再用Admin SDK查询Firestore(Admin SDK默认绕过安全规则,你可以自行在服务端做权限校验)
示例核心逻辑:
客户端登录成功后写入cookie:
// 客户端登录成功后执行 const idToken = await user.getIdToken(); document.cookie = `firebaseToken=${idToken}; path=/; max-age=86400`;
服务端getServerSideProps逻辑:
// 先初始化Firebase Admin SDK,注意服务账号密钥仅可在服务端使用,禁止泄露到前端 import admin from 'firebase-admin'; if (!admin.apps.length) { admin.initializeApp({ credential: admin.credential.cert(你的服务账号密钥配置), }); } export async function getServerSideProps(context) { // 从cookie中获取身份token const token = context.req.cookies.firebaseToken; if (!token) { return { redirect: { destination: '/login', permanent: false } }; } try { // 验证token有效性 const decodedToken = await admin.auth().verifyIdToken(token); const uid = decodedToken.uid; // 用Admin SDK查询Firestore const conversationDoc = await admin.firestore().collection('conversations').doc(context.params.conversationid).get(); if (!conversationDoc.exists) { return { notFound: true }; } // 自行补充权限校验:例如判断当前用户是否属于该会话 const conversation = conversationDoc.data(); if (!conversation.members.includes(uid)) { return { notFound: true }; } return { props: { conversationData: conversation, }, }; } catch (e) { return { redirect: { destination: '/login', permanent: false } }; } }
方案选择建议
如果你的聊天页面不需要做SEO,优先选方案1,改动最小,和你之前写纯React的逻辑一致,也不需要处理服务端密钥等安全问题。如果需要SSR提升首屏加载速度或者做SEO,再选方案2。
内容的提问来源于stack exchange,提问作者squish
相关产品推荐
相关产品推荐

