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 }); } }
关键注意事项
- 确保
authOptions在服务端可访问,getServerSession能正确获取session。 - 客户端的
window.location.href是为了避免signOut的callbackUrl因异步延迟不生效。 - 服务端必须通过抛出错误的方式触发重定向,不能在拦截器内直接调用
redirect(服务端组件的redirect必须在组件渲染同步流程中执行)。
内容的提问来源于stack exchange,提问作者Parth Nariya
相关产品推荐
相关产品推荐

