Next.js服务端如何刷新过期JWT Token?
在Next.js服务端实现JWT自动刷新逻辑
我已经在客户端实现了JWT Token过期自动刷新的逻辑:从Cookie获取JWT和Refresh Token,判断JWT是否过期,若过期则调用刷新接口获取新Token并更新Cookie,再携带新Token发起请求。但不知道怎么在Next.js服务端实现相同逻辑,以下是我的客户端代码:
function getAuthTokenFromCookie() { const cookieString = document.cookie; const cookies = cookieString.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.startsWith('jwt_token=')) { const token = cookie.substring('jwt_token='.length); return token; } } return null; } function getRefreshTokenFromCookie() { const cookieString = document.cookie; const cookies = cookieString.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.startsWith('refresh_token=')) { const token = cookie.substring('refresh_token='.length); return token; } } return null; } function setAuthTokenInCookie(token) { // Set the JWT token in the cookie document.cookie = `jwt_token=${token}; path=/`; } export async function handleAxois(url, method = 'get', options=null) { const token = getAuthTokenFromCookie(); const refreshToken = getRefreshTokenFromCookie(); const CSRF_TOKEN = getCSRFTokenFromCookie() if (token) { const tokenParts = token.split('.'); if (tokenParts.length === 3) { const payload = JSON.parse(atob(tokenParts[1])); const currentTimestamp = Math.floor(Date.now() / 1000); if (payload.exp < currentTimestamp) { console.log("payload", payload.exp) console.log("currentTimestamp", currentTimestamp) // Token has expired, refresh the token if (refreshToken) { return axios .post(`${domainHost(window.location.hostname)}/api/token/refresh/`, { refresh: refreshToken }) //here must be provide refresh url .then((response) => { const newToken = response.data.access; // Update the token in the cookie setAuthTokenInCookie(newToken); console.log("token expaired") let headers = { 'Accept': 'application/json', 'Authorization': `Bearer ${newToken}`, 'Content-Type': 'multipart/form-data', 'X-CSRFToken':CSRF_TOKEN, }; return axios({ method, url, withCredentials: true, headers, credentials: 'include', data: options , }); }); } else { // Handle the case when refresh token is missing console.log('Refresh token not found'); return Promise.reject(new Error('Refresh token not found')); } } else { // Token is still valid, proceed with the request console.log("token not expaired") let headers = { 'Accept': 'application/json', 'Authorization': `Bearer ${token}`, 'Content-Type': 'multipart/form-data', 'X-CSRFToken':CSRF_TOKEN, }; return axios({ method, url, withCredentials: true, headers, credentials: 'include', data: options , }); } } } return Promise.reject(new Error('Token not found')); }
客户端调用示例:
handleAxois(`${domainHost(window.location.hostname)}/shop-details/`).then((res)=>{ console.log(res) setStoreData(res.data) }).catch((err)=>{ console.log(err) })
服务端实现方案
Next.js服务端环境(如getServerSideProps、API路由、App Router的Server Components/Actions)无法直接使用浏览器的document.cookie,需通过请求头解析Cookie,通过响应头更新Cookie,以下是适配实现:
1. 封装服务端工具函数
推荐用nookies简化Cookie操作,也可手动实现:
import { parseCookies, setCookie } from 'nookies'; // 从请求头解析Cookie function getAuthTokensFromServerCookie(req) { const cookies = parseCookies({ req }); return { jwtToken: cookies.jwt_token || null, refreshToken: cookies.refresh_token || null, csrfToken: cookies.csrf_token || null }; } // 校验JWT是否过期 function isJwtExpired(token) { if (!token) return true; try { const tokenParts = token.split('.'); if (tokenParts.length !== 3) return true; const payload = JSON.parse(atob(tokenParts[1])); const currentTimestamp = Math.floor(Date.now() / 1000); return payload.exp < currentTimestamp; } catch (err) { return true; } } // 服务端设置Cookie function setAuthTokenInServerCookie(res, token) { setCookie({ res }, 'jwt_token', token, { path: '/', httpOnly: true, // 提升安全性,禁止前端读取 secure: process.env.NODE_ENV === 'production', // 生产环境强制HTTPS sameSite: 'strict' }); }
2. 服务端请求封装函数
import axios from 'axios'; export async function handleServerRequest(url, method = 'get', options = null, { req, res }) { const { jwtToken, refreshToken, csrfToken } = getAuthTokensFromServerCookie(req); if (!jwtToken) { return Promise.reject(new Error('Token not found')); } // 检查JWT是否过期 if (isJwtExpired(jwtToken)) { if (!refreshToken) { return Promise.reject(new Error('Refresh token not found')); } // 调用刷新接口 try { const refreshResponse = await axios.post( `${process.env.NEXT_PUBLIC_API_DOMAIN}/api/token/refresh/`, { refresh: refreshToken }, { headers: { 'X-CSRFToken': csrfToken, Cookie: req.headers.cookie }, withCredentials: true } ); const newToken = refreshResponse.data.access; // 更新服务端Cookie setAuthTokenInServerCookie(res, newToken); // 携带新Token发起原请求 return axios({ method, url, headers: { 'Accept': 'application/json', 'Authorization': `Bearer ${newToken}`, 'X-CSRFToken': csrfToken, 'Content-Type': options ? 'multipart/form-data' : 'application/json' }, withCredentials: true, data: options }); } catch (refreshErr) { return Promise.reject(refreshErr); } } else { // Token有效,直接发起请求 return axios({ method, url, headers: { 'Accept': 'application/json', 'Authorization': `Bearer ${jwtToken}`, 'X-CSRFToken': csrfToken, 'Content-Type': options ? 'multipart/form-data' : 'application/json' }, withCredentials: true, data: options }); } }
3. 应用场景示例
在getServerSideProps中使用
export async function getServerSideProps(context) { const { req, res } = context; try { const response = await handleServerRequest( `${process.env.NEXT_PUBLIC_API_DOMAIN}/shop-details/`, 'get', null, { req, res } ); return { props: { storeData: response.data } }; } catch (err) { console.error(err); return { props: { storeData: null } }; } }
在API路由中使用
// pages/api/shop-details.js export default async function handler(req, res) { try { const apiResponse = await handleServerRequest( `${process.env.NEXT_PUBLIC_API_DOMAIN}/shop-details/`, req.method, req.body, { req, res } ); res.status(200).json(apiResponse.data); } catch (err) { res.status(err.response?.status || 500).json({ error: err.message }); } }
注意事项
- 服务端操作Cookie必须依赖
req(解析)和res(设置)对象 - 生产环境务必给Cookie添加
httpOnly、secure、sameSite属性,避免XSS和CSRF风险 - 刷新接口需传递原Cookie和CSRF Token,确保身份验证有效
内容的提问来源于stack exchange,提问作者boyenec
相关产品推荐
相关产品推荐

