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

Next.js 14中Axios响应拦截器服务端组件用户登出问题求助

解决Next.js 14中Axios拦截器服务端/客户端统一登出并重定向问题

核心问题分析

next-auth/react的signOut方法依赖浏览器环境,无法在服务端直接生效。要实现两端统一的登出逻辑,需要拆分客户端/服务端处理流程:客户端直接调用signOut,服务端通过抛出自定义错误并在调用处捕获处理重定向。

解决方案代码实现

1. 定义自定义错误类型

用于在服务端拦截器中标记需要登出的场景:

class UnauthorizedError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'UnauthorizedError';
  }
}

2. 修改Axios拦截器逻辑

区分客户端/服务端场景,分别处理登出:

import axios, { AxiosError } from 'axios';
import { getSession, signOut } from 'next-auth/react';
import { getServerSession } from 'next-auth/next';
import { authOptions } from '@/app/api/auth/[...nextauth]/route'; // 替换为你的authOptions路径

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
const axiosInstance = axios.create({ baseURL: BASE_URL });

axiosInstance.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config;
    let session;

    // 获取当前环境的session
    if (typeof window !== "undefined") {
      session = await getSession();
    } else {
      session = await getServerSession(authOptions);
    }

    const isUnauthorized = error.response?.status === 401 || 
                          error.message === "Request failed with status code 401";

    if (isUnauthorized) {
      if (session && session.refreshToken) {
        try {
          const refreshResponse = await axios.post(
            `${BASE_URL}/v1/auth/refresh-token`,
            {
              refreshToken: session.refreshToken, // 注意:原代码的`avc`疑似笔误,需检查是否为业务需要
            }
          );
          const { data } = refreshResponse;
          
          if (data.access_token && originalRequest) {
            originalRequest.headers.Authorization = `Bearer ${data.access_token}`;
            return await axios(originalRequest);
          } else {
            // 刷新失败,执行登出
            handleLogout();
          }
        } catch (e) {
          // 刷新请求报错,执行登出
          handleLogout();
        }
      } else {
        // 无有效session,执行登出
        handleLogout();
      }
    }

    return Promise.reject(error);

    // 拆分登出逻辑函数
    async function handleLogout() {
      if (typeof window !== "undefined") {
        // 客户端:直接调用signOut并强制跳转
        await signOut({ callbackUrl: '/login' });
        window.location.href = '/login'; // 确保跳转生效
      } else {
        // 服务端:抛出自定义错误,交由调用方处理重定向
        throw new UnauthorizedError('Session expired, please re-login');
      }
    }
  }
);

export default axiosInstance;

3. 服务端组件/API路由中捕获错误并处理重定向

  • 服务端组件:使用next/navigation的redirect方法
import { redirect } from 'next/navigation';
import axiosInstance from '@/lib/axios';
import { signOut } from 'next-auth/react';

export default async function ProtectedServerComponent() {
  try {
    const response = await axiosInstance.get('/protected-data');
    return <div>{JSON.stringify(response.data)}</div>;
  } catch (error) {
    if (error instanceof UnauthorizedError) {
      // 清除服务端session后重定向
      await signOut({ redirect: false });
      redirect('/login');
    }
    // 处理其他错误
    return <div>Something went wrong</div>;
  }
}
  • API路由:返回NextResponse重定向
import { NextApiRequest, NextApiResponse } from 'next';
import axiosInstance from '@/lib/axios';
import { signOut } from 'next-auth/react';
import { NextResponse } from 'next/server';

export async function GET(req: NextApiRequest) {
  try {
    const response = await axiosInstance.get('/protected-data');
    return NextResponse.json(response.data);
  } catch (error) {
    if (error instanceof UnauthorizedError) {
      // 清除session后返回重定向响应
      const signOutRes = await signOut({ redirect: false });
      return NextResponse.redirect(new URL('/login', req.url), {
        headers: signOutRes.headers,
      });
    }
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}

关键注意事项

  1. 确保authOptions在服务端可访问,getServerSession能正确获取session。
  2. 客户端的window.location.href是为了避免signOut的callbackUrl因异步延迟不生效。
  3. 服务端必须通过抛出错误的方式触发重定向,不能在拦截器内直接调用redirect(服务端组件的redirect必须在组件渲染同步流程中执行)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:05:00