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

Next.js中间件中过期Token处理及Axios适配错误解决

问题解决思路与认证方案优化

一、Axios请求错误的解决方法

你遇到的Axios错误是因为Next.js中间件运行在Edge Runtime环境,Axios默认的XHR/HTTP适配器不支持这个环境。直接替换为Edge原生支持的fetch API即可解决,无需额外依赖:

async function refreshAccessToken(refresh: string | undefined): Promise<string | null> {
  if (!refresh) return null;
  
  const refreshTokenApi = `${process.env.NEXT_PUBLIC_HOST}${process.env.NEXT_PUBLIC_REFRESH_TOKEN_API}`;
  try {
    const response = await fetch(refreshTokenApi, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ refresh }),
    });

    if (!response.ok) throw new Error('刷新Token失败');
    
    const data = await response.json();
    return data.accessToken;
  } catch (error) {
    console.error("Error refreshing access token:", error);
    return null;
  }
}

二、当前认证方案的问题与优化

你的代码存在多处逻辑漏洞,以下是修复和优化要点:

1. 错误的Cookie操作逻辑

  • 原代码中request.cookies.clear()会清空所有Cookie,包括可用的refreshToken,导致无法刷新AccessToken。仅在refreshToken失效时才清空Cookie。
  • Next.js中间件无法直接修改request.cookies,必须通过NextResponse对象设置新Cookie。

2. 错误处理顺序问题

原代码先console.log(errorJson)再定义变量,会触发ReferenceError,需调整执行顺序。

3. 路由保护逻辑通用性不足

原代码仅针对/open-bo-account路由,建议用数组定义所有需要保护的路由,方便后续扩展维护。

4. 缺少refreshToken有效性校验

调用刷新接口前,需先判断refreshToken是否存在,避免无效请求。

优化后的完整中间件代码

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { jwtVerify, TokenExpiredError } from 'jose';

// 定义需要保护的路由列表
const protectedRoutes = ['/open-bo-account'];

async function refreshAccessToken(refresh: string | undefined): Promise<string | null> {
  if (!refresh) return null;
  
  const refreshTokenApi = `${process.env.NEXT_PUBLIC_HOST}${process.env.NEXT_PUBLIC_REFRESH_TOKEN_API}`;
  try {
    const response = await fetch(refreshTokenApi, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ refresh }),
    });

    if (!response.ok) throw new Error('刷新Token失败');
    
    const data = await response.json();
    return data.accessToken;
  } catch (error) {
    console.error("Error refreshing access token:", error);
    return null;
  }
}

export default async function middleware(request: NextRequest) {
  const accessToken = request.cookies.get("access")?.value;
  const refreshToken = request.cookies.get("refresh")?.value;
  const secret_key = process.env.SECRET_KEY;
  
  // 判断当前请求是否在保护路由范围内
  const isProtectedRoute = protectedRoutes.some(route => 
    request.nextUrl.pathname.startsWith(route)
  );

  if (!isProtectedRoute) {
    return NextResponse.next();
  }

  // 无AccessToken,直接跳转登录页
  if (!accessToken) {
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.clear();
    return response;
  }

  try {
    if (!secret_key) throw new Error('缺少SECRET_KEY环境变量');
    
    const secret = new TextEncoder().encode(secret_key);
    await jwtVerify(accessToken, secret);
    // Token有效,直接放行
    return NextResponse.next();
  } catch (error) {
    // 处理Token过期错误
    if (error instanceof TokenExpiredError) {
      // 无refreshToken,跳转登录页
      if (!refreshToken) {
        const response = NextResponse.redirect(new URL('/login', request.url));
        response.cookies.clear();
        return response;
      }

      // 尝试刷新AccessToken
      const newAccessToken = await refreshAccessToken(refreshToken);
      if (newAccessToken) {
        // 创建新响应并设置新的accessToken Cookie
        const response = NextResponse.next();
        response.cookies.set("access", newAccessToken, {
          httpOnly: true, // 开启httpOnly防止XSS攻击
          secure: process.env.NODE_ENV === 'production', // 生产环境强制HTTPS传输
          sameSite: 'strict', // 防止CSRF攻击
          path: '/'
        });
        return response;
      }
    }

    // 其他错误(如refresh失败、Token无效),跳转登录页并清空Cookie
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.clear();
    return response;
  }
}

// 配置中间件匹配的路由(与protectedRoutes对应)
export const config = {
  matcher: ['/open-bo-account/:path*'],
};

三、额外安全建议

  • Cookie属性强化:给accessToken和refreshToken添加httpOnly、secure、sameSite属性,提升安全性。
  • RefreshToken旋转机制:后端返回新的refreshToken替换旧值,降低Token泄露风险。
  • 环境变量配置:SECRET_KEY属于敏感信息,不要使用NEXT_PUBLIC_前缀,避免暴露给客户端。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 18:10:54