Next.js:getServerSideProps中访问更新客户端存储Token的问题
服务端渲染(SSR)下JWT认证解决方案
核心问题
getServerSideProps运行在Node.js环境,无法访问浏览器的localStorage,导致无法获取客户端存储的Token/Refresh Token- 现有请求逻辑依赖浏览器API(
localStorage、Router),直接在服务端调用会抛出错误 - 商品页SSR需要认证数据,但服务端无法携带客户端的认证信息发起请求
解决方案
方案一:Cookie传递Token(推荐)
将Token同时存储在Cookie和localStorage中,服务端通过请求Cookie获取Token,解决服务端认证问题。
1. 修改客户端Token存储逻辑
登录成功后,把Token和Refresh Token同时写入localStorage和Cookie:
// 登录成功回调 const handleLoginSuccess = (response) => { const { token, refreshToken } = response.data; // 存储到localStorage localStorage.setItem('token', token); localStorage.setItem('refreshToken', refreshToken); // 写入Cookie(可使用js-cookie库简化操作,或原生document.cookie) document.cookie = `token=${token}; path=/; secure; sameSite=Strict; maxAge=86400`; document.cookie = `refreshToken=${refreshToken}; path=/; secure; sameSite=Strict; maxAge=604800`; };
2. 调整getServerSideProps获取Token
从请求上下文的req.cookies中提取Token,传入数据请求函数:
export async function getServerSideProps(context) { const { locale, params, req } = context; const { uuid } = params; const token = req.cookies.token || ''; try { const data = await fetchProductData(uuid, locale, token); return { props: { uuid, data } }; } catch (error) { // 处理401未授权,重定向到登录页 if (error.message.includes('401')) { return { redirect: { destination: '/login', permanent: false } }; } return { props: { uuid, data: null, error: true } }; } }
3. 修改请求函数支持服务端Token传入
更新fetchProductData、getData和executeRequest,区分服务端/客户端环境,避免浏览器API报错:
// fetchProductData新增token参数 export const fetchProductData = (uuid, locale, token = '') => { return new Promise((resolve, reject) => { getData( { url: `${BACKEND_URL}/v1/items/${uuid}`, params: { language: locale, program: "web" }, token: token }, (responseData, status) => { status === 200 ? resolve(responseData) : reject(new Error(`Failed to fetch: ${status}`)); } ); }); }; // getData传递token参数 export const getData = (fetchData, callback) => { const { url, params = null, token = '' } = fetchData; executeRequest({ url, method: "GET", params }, callback, token); }; // executeRequest区分服务端/客户端逻辑 const executeRequest = async (request, callback, paramToken = "") => { // 服务端环境:直接使用传入的Token,不处理刷新(无法更新客户端存储) if (typeof window === 'undefined') { const token = paramToken; try { const response = await axios({ ...request, headers: { Authorization: token ? `Bearer ${token}` : "" } }); callback(response.data, 200); } catch (error) { callback(null, error.response?.status); } return; } // 客户端环境:保留原有Token刷新逻辑 let token = paramToken || localStorage.getItem("token"); let refreshTokenBlocked = false; try { const response = await axios({ ...request, headers: { Authorization: token ? `Bearer ${token}` : "" } }); callback(response.data, 200); } catch (error) { // 原有客户端错误处理逻辑(Token刷新、队列请求等) if (error.response?.status === 401) { if (token && !refreshInProgress) { refreshInProgress = true; try { const refreshToken = localStorage.getItem("refreshToken"); const refreshResponse = await refreshTokenFunction(refreshToken); if (refreshResponse && refreshResponse !== "blocked") { refreshInProgress = false; token = refreshResponse; await Promise.all( requestQueue.map(async (queuedRequest) => { await executeRequest(queuedRequest.request, queuedRequest.callback, refreshResponse); }) ); requestQueue = []; requestCounter = 0; } else { refreshTokenBlocked = true; } } catch (err) { callback(null, err?.response?.status); } } requestQueue.push({ request: { ...request }, callback }); requestCounter++; } else if (error.response?.status === 403) { store.dispatch(setIsBlocked(true)); Router.push("/blocked"); } else { callback("err", error.response?.status, error.response?.data); } } finally { if (!refreshTokenBlocked) { requestCounter--; if (requestCounter === 0 && requestQueue.length > 0) { const queuedRequest = requestQueue.shift(); executeRequest(queuedRequest.request, queuedRequest.callback); } } } };
方案二:公开数据免认证(简单场景)
如果商品信息是公开可访问的,无需用户登录即可查看,直接修改后端接口,取消商品详情接口的Token校验。这种方案无需修改前端逻辑,直接在getServerSideProps中发起无认证请求即可。
方案三:集成NextAuth.js(标准化认证)
使用NextAuth.js统一处理服务端和客户端的认证状态,它会自动将用户会话存储在HttpOnly Cookie中,服务端可通过getServerSession获取认证信息,快速对接后端接口。
关键注意事项
- 服务端无法更新客户端的
localStorage和Cookie,因此Token刷新逻辑只能在客户端执行 - 所有依赖浏览器API的代码(如
localStorage、Router)必须用typeof window !== 'undefined'做环境判断,避免服务端渲染报错 - Cookie需设置
Secure、SameSite=Strict属性,提升安全性
内容的提问来源于stack exchange,提问作者Uzuk
相关产品推荐
相关产品推荐

