Next.js getServerSideProps访问localStorage及JWT鉴权方案咨询
一、现有鉴权方案的设计缺陷
- 鉴权覆盖场景不全:你当前从
req.body读取token的逻辑,仅能覆盖带请求体的POST/PUT等类别的数据请求,所有GET类请求(包括页面初始访问、路由跳转、静态资源加载)没有请求体,middleware完全拿不到token,会直接出现鉴权逻辑漏判。 - 执行逻辑存在运行时矛盾:middleware和getServerSideProps都运行在服务端环境,根本没有权限访问客户端的localStorage,你设计的「校验不通过则清除localStorage中token」的逻辑在服务端完全无法执行,最终会出现用户已经被重定向到登录页,但本地localStorage里的失效token仍然残留,下次访问重复触发校验失败、重定向的死循环问题。
- 鉴权边界设计错误:你把鉴权触发点绑定在「发起数据库请求」节点,意味着所有不需要查库的受保护路由完全处于裸奔状态,未登录用户可以直接访问这类页面,达不到路由级访问控制的要求。
- token传递方式不规范:将token放在请求体中传递,不符合通用鉴权规范,会遇到文件上传、form表单提交等场景请求体被占用、跨域请求漏传body、代理层/日志明文截获body内容等问题,稳定性和安全性都不足。
二、SSR场景下的身份校验实现方案
核心前提:服务端运行时不存在浏览器API,无法读取localStorage,因此需要将鉴权凭证存储在浏览器会自动随每次请求携带的介质中,也就是Cookie,从根源解决服务端拿不到凭证的问题,不需要依赖客户端useEffect发请求二次校验,就能实现完整的服务端渲染鉴权。
具体实现步骤
- 调整token存储逻辑
用户登录接口校验通过后,不要将JWT返回给前端存入localStorage,而是直接通过响应头Set-Cookie将token写入Cookie,同时给Cookie加上HttpOnly、Secure(生产环境必填)、SameSite=Lax、Path=/属性:HttpOnly属性标记的Cookie无法被客户端JS读取,能彻底规避XSS攻击窃取token的风险,安全性远高于localStorage存储方案。
配置完成后,不管是用户首次访问页面的服务端渲染请求,还是客户端路由跳转的请求,浏览器都会自动将Cookie携带在请求头中,middleware和getServerSideProps可以直接从请求对象上解析到token,不需要客户端手动传递。 - 抽离通用鉴权工具方法
把token解析、校验的逻辑抽成独立的公共方法,避免重复编码:// utils/auth.js const jwt = require('jsonwebtoken') module.exports = function getAuthUser(req) { // 直接从请求附带的cookie中取token const token = req.cookies?.auth_token if (!token) return null try { // 用服务端存储的密钥校验token合法性 const userInfo = jwt.verify(token, process.env.JWT_SECRET) return userInfo } catch (err) { // token过期、签名不合法都直接返回未登录状态 return null } } - 在getServerSideProps中完成校验与渲染
受保护的页面在getServerSideProps阶段调用上述鉴权方法,校验失败则先清除失效Cookie,再返回重定向响应;校验通过则正常查询页面数据,连同用户信息一起作为props传给页面完成服务端渲染:// pages/protected-page.js const getAuthUser = require('../utils/auth') export async function getServerSideProps(context) { const user = getAuthUser(context.req) if (!user) { // 先清除失效的鉴权cookie context.res.setHeader( 'Set-Cookie', 'auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' ) // 重定向到登录页 return { redirect: { destination: '/login', permanent: false, } } } // 校验通过,正常查询页面渲染所需的服务端数据 // const pageData = await db.query(...) return { props: { user, // ...pageData } } } export default function ProtectedPage({ user }) { // 直接拿服务端返回的数据渲染,不需要客户端再做校验 return <div>当前登录用户:{user.name}</div> } - 可选优化:全局middleware统一拦截
对于有大量受保护路由的应用,可以在根middleware中配置路由匹配规则,对受保护路由统一做token校验,不需要每个页面单独写鉴权逻辑,校验不通过直接返回重定向+清Cookie的响应,减少重复代码的同时提升响应速度。
注意事项
- 绝对不要在getServerSideProps、middleware这类服务端代码中尝试访问
window、localStorage等浏览器API,运行时会直接报错。 - JWT的签名密钥必须存储在服务端环境变量中,绝对不能泄露到客户端代码里。
- 如果需要兼容存量localStorage存储token的用户,可以在客户端_app的首次加载逻辑中加一层判断:如果localStorage中存在有效token、但Cookie中没有对应凭证,就发一次接口请求将token同步写入Cookie,后续完全走Cookie鉴权流程即可,长期建议彻底废弃localStorage存鉴权凭证的方案。
- 鉴权失败重定向时必须同步清除失效Cookie,避免残留的无效token导致重复校验失败、循环重定向的问题。
内容的提问来源于stack exchange,提问作者Vraj Solanki
相关产品推荐
相关产品推荐

