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

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>;
}

关键注意事项

  1. Cookie配置:登录成功后必须将auth_token和refresh_token存入HTTP-only、Secure的Cookie中,这是服务端能获取令牌的前提,同时避免XSS攻击。
  2. 服务端写Cookie限制:服务端组件无法直接修改Cookie,必须通过Server Action或API路由完成。
  3. 避免无限循环:拦截器中保留_retry标记,防止401刷新逻辑无限触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 03:15:21