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

Next.js 13结合自定义后端实现认证令牌刷新问题求助

解决Next.js 13中刷新Access Token时的Cookie更新问题

核心问题分析

Next.js 13的App Router中,cookies()函数仅能在Server Actions或**路由处理器(API Routes)**中直接操作Cookie。Middleware、前端Axios拦截器这类环境无法直接调用cookies().set(),这是你遇到错误的根本原因。


解决思路1:修正Middleware的Cookie操作方式

Middleware中不能直接调用你的refreshToken Server Action来更新Cookie,而是要手动通过NextResponse修改响应头设置Cookie。步骤如下:

  1. 在Middleware中解析现有Cookie,判断是否需要刷新Token;
  2. 直接调用Django后端的刷新接口,获取新的Token;
  3. 构造新的Session内容,通过NextResponse的setCookie方法更新Cookie;
  4. 返回修改后的响应。

示例代码:

export async function middleware(request: NextRequest) {
  const sessionCookie = request.cookies.get('session')?.value;
  let response = NextResponse.next();

  if (sessionCookie) {
    const session: UserState = JSON.parse(sessionCookie);
    
    // 处理refresh token过期的情况
    if (session.refresh_expiration && new Date(session.refresh_expiration).getTime() <= Date.now()) {
      response = NextResponse.redirect(new URL('/', request.url));
      response.cookies.delete('session');
      return response;
    }

    // 处理access token过期,需要刷新的情况
    if (session.access_expiration && new Date(session.access_expiration).getTime() <= Date.now()) {
      try {
        // 直接调用Django的刷新接口
        const refreshRes = await fetch(`${process.env.NEXT_PUBLIC_DJANGO_API}/auth/refresh/`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ refresh: session.refresh }),
        });

        if (refreshRes.ok) {
          const newTokens = await refreshRes.json();
          // 更新session内容
          const updatedSession = {
            ...session,
            access: newTokens.access,
            access_expiration: newTokens.access_expiration,
          };
          // 设置新的Cookie到响应中
          response.cookies.set('session', JSON.stringify(updatedSession), {
            expires: new Date(session.refresh_expiration || ''),
            httpOnly: true,
            path: '/',
            sameSite: 'strict',
          });
        } else {
          // 刷新失败,清除Cookie并跳转登录
          response = NextResponse.redirect(new URL('/', request.url));
          response.cookies.delete('session');
        }
      } catch (err) {
        console.error('Token刷新失败:', err);
        response = NextResponse.redirect(new URL('/', request.url));
        response.cookies.delete('session');
      }
    }
  }

  return response;
}

解决思路2:修复API路由的Cookie操作

如果用API路由处理刷新,需要注意:

  • 从NextRequest中获取Cookie,避免环境差异导致的Cookie读取失败;
  • 通过NextResponse的setCookie方法更新Cookie,而非依赖Server Action的工具函数。

示例API路由app/api/auth/refresh/route.ts:

import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const sessionCookie = request.cookies.get('session')?.value;
  if (!sessionCookie) {
    return NextResponse.json({ error: '无有效会话' }, { status: 401 });
  }

  const session: UserState = JSON.parse(sessionCookie);
  try {
    const { data } = await refreshTokenService(session.refresh || '');
    if (data) {
      const updatedSession = {
        ...session,
        access: data.access,
        access_expiration: data.access_expiration,
      };
      const response = NextResponse.json({ success: true });
      response.cookies.set('session', JSON.stringify(updatedSession), {
        expires: new Date(session.refresh_expiration || ''),
        httpOnly: true,
        path: '/',
        sameSite: 'strict',
      });
      return response;
    } else {
      return NextResponse.json({ error: '刷新失败' }, { status: 400 });
    }
  } catch (err) {
    const response = NextResponse.json({ error: '服务器错误' }, { status: 500 });
    response.cookies.delete('session');
    return response;
  }
}

前端调用该API时,浏览器会自动保存HttpOnly Cookie,无需手动处理。


解决思路3:Axios拦截器的正确用法

前端Axios拦截器无法直接操作HttpOnly Cookie,正确做法是:当请求返回401(Token过期)时,调用上述API路由或Server Action刷新Token,再重试原请求。

示例Axios拦截器代码:

import axios from 'axios';

const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_DJANGO_API,
});

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    // 只处理401且未重试过的请求
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        // 调用API路由刷新Token
        await axios.post('/api/auth/refresh');
        // 重试原请求
        return api(originalRequest);
      } catch (refreshErr) {
        // 刷新失败,跳转登录页
        window.location.href = '/';
        return Promise.reject(refreshErr);
      }
    }
    return Promise.reject(error);
  }
);

export default api;

关键注意事项

  • HttpOnly Cookie的安全性:HttpOnly Cookie无法被前端JS读取,所有Cookie操作必须在Server端完成;
  • Server Action的调用限制:Server Action只能在React组件的交互事件(如onClick)或Server Components中调用,Middleware和前端普通JS环境无法直接调用它修改Cookie;
  • Cookie的路径和SameSite属性:设置Cookie时要确保path: '/'和合适的sameSite值,避免Cookie无法跨路由生效。

内容的提问来源于stack exchange,提问作者Luc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 07:50:57