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
相关产品推荐
相关产品推荐

