NextJS服务端Axios拦截器如何获取客户端存储的认证令牌
解决方案
核心思路
服务端组件无法直接访问客户端状态(如zustand),但可以通过HTTP-only Cookie获取令牌——这是服务端获取用户身份凭证的标准方式,同时能避免XSS风险。以下方案尽量保留你现有axios拦截器架构,仅调整令牌的来源逻辑。
方案1:分环境注入令牌的axios实例工厂
封装一个axios实例创建函数,根据运行环境(客户端/服务端)自动从对应位置获取令牌,拦截器逻辑复用现有代码:
// utils/axios.ts import axios from 'axios'; import { cookies } from 'next/headers'; import { useAuthStore } from '@/stores/auth'; // 创建axios实例的工厂函数 export function createAxiosInstance() { const instance = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL, }); // 请求拦截器:自动添加Authorization头 instance.interceptors.request.use((config) => { let authToken; if (typeof window !== 'undefined') { // 客户端:从zustand取令牌 const authStore = useAuthStore.getState(); authToken = authStore.authToken; } else { // 服务端:从Cookie取令牌 const cookieStore = cookies(); authToken = cookieStore.get('auth_token')?.value; } if (authToken) { config.headers.Authorization = `Bearer ${authToken}`; } return config; }); // 响应拦截器:复用你的令牌刷新逻辑,调整刷新令牌来源 instance.interceptors.response.use( (res) => res, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; let refreshToken; if (typeof window !== 'undefined') { // 客户端:从zustand取刷新令牌 const authStore = useAuthStore.getState(); refreshToken = authStore.refreshToken; } else { // 服务端:从Cookie取刷新令牌 const cookieStore = cookies(); refreshToken = cookieStore.get('refresh_token')?.value; } if (refreshToken) { // 调用刷新接口 const refreshRes = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/refresh`, { refresh_token: refreshToken, }); const newAuthToken = refreshRes.data.auth_token; const newRefreshToken = refreshRes.data.refresh_token; // 更新令牌存储 if (typeof window !== 'undefined') { // 客户端:更新zustand和Cookie const authStore = useAuthStore.getState(); authStore.setTokens(newAuthToken, newRefreshToken); document.cookie = `auth_token=${newAuthToken}; path=/; httpOnly; secure; sameSite=strict`; document.cookie = `refresh_token=${newRefreshToken}; path=/; httpOnly; secure; sameSite=strict`; } else { // 服务端:用Server Action更新Cookie await updateAuthCookies(newAuthToken, newRefreshToken); } // 重新发起原请求 originalRequest.headers.Authorization = `Bearer ${newAuthToken}`; return instance(originalRequest); } } return Promise.reject(error); } ); return instance; } // Server Action:服务端更新Cookie专用 'use server'; import { cookies } from 'next/headers'; export async function updateAuthCookies(newAuthToken: string, newRefreshToken: string) { cookies().set('auth_token', newAuthToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 3600, // 1小时 }); cookies().set('refresh_token', newRefreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 86400 * 7, // 7天 }); }
使用方式
- 客户端组件:直接调用
createAxiosInstance()创建实例,自动从zustand取令牌 - 服务端组件:同样调用
createAxiosInstance(),自动从Cookie取令牌,无需额外配置
方案2:API路由代理请求(彻底隔离服务端令牌逻辑)
如果不想在服务端组件中处理axios拦截器,可以将服务端组件的API请求通过Next.js内部API路由代理,所有令牌逻辑放在API路由中处理:
// app/api/proxy/[...path]/route.ts import { cookies } from 'next/headers'; import axios from 'axios'; import { NextResponse } from 'next/server'; export async function GET(request: Request, { params }: { params: { path: string[] } }) { const cookieStore = cookies(); const authToken = cookieStore.get('auth_token')?.value; const refreshToken = cookieStore.get('refresh_token')?.value; const targetUrl = `${process.env.NEXT_PUBLIC_API_URL}/${params.path.join('/')}`; const instance = axios.create({ headers: { Authorization: authToken ? `Bearer ${authToken}` : '', }, }); try { const res = await instance.get(targetUrl); return NextResponse.json(res.data); } catch (error: any) { // 处理401令牌刷新 if (error.response?.status === 401 && refreshToken) { const refreshRes = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/refresh`, { refresh_token: refreshToken, }); const newAuthToken = refreshRes.data.auth_token; const newRefreshToken = refreshRes.data.refresh_token; // 更新Cookie cookieStore.set('auth_token', newAuthToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 3600, }); cookieStore.set('refresh_token', newRefreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 86400 * 7, }); // 重新发起请求 const retryRes = await axios.get(targetUrl, { headers: { Authorization: `Bearer ${newAuthToken}` }, }); return NextResponse.json(retryRes.data); } return NextResponse.json(error.response?.data || { message: '请求失败' }, { status: error.response?.status || 500 }); } }
使用方式
服务端组件直接调用内部API路由,无需关心令牌逻辑:
export async function ServerComponent() { const res = await fetch('/api/proxy/protected-data'); const data = await res.json(); return <div>{JSON.stringify(data)}</div>; }
关键注意事项
- Cookie配置:登录成功后必须将
auth_token和refresh_token存入HTTP-only、Secure的Cookie中,这是服务端能获取令牌的前提,同时避免XSS攻击。 - 服务端写Cookie限制:服务端组件无法直接修改Cookie,必须通过Server Action或API路由完成。
- 避免无限循环:拦截器中保留
_retry标记,防止401刷新逻辑无限触发。
内容的提问来源于stack exchange,提问作者Prajwal Kulkarni
相关产品推荐
相关产品推荐

