You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:20:54