Next.js 13结合自定义后端实现认证令牌刷新问题求助
核心问题分析
Next.js 13的App Router中,cookies()函数仅能在Server Actions或**路由处理器(API Routes)**中直接操作Cookie。Middleware、前端Axios拦截器这类环境无法直接调用cookies().set(),这是你遇到错误的根本原因。
解决思路1:修正Middleware的Cookie操作方式
Middleware中不能直接调用你的refreshToken Server Action来更新Cookie,而是要手动通过NextResponse修改响应头设置Cookie。步骤如下:
- 在Middleware中解析现有Cookie,判断是否需要刷新Token;
- 直接调用Django后端的刷新接口,获取新的Token;
- 构造新的Session内容,通过
NextResponse的setCookie方法更新Cookie; - 返回修改后的响应。
示例代码:
export async function middleware(request: NextRequest) { const sessionCookie = request.cookies.get('session')?.value; let response = NextResponse.next(); if (sessionCookie) { const session: UserState = JSON.parse(sessionCookie); // 处理refresh token过期的情况 if (session.refresh_expiration && new Date(session.refresh_expiration).getTime() <= Date.now()) { response = NextResponse.redirect(new URL('/', request.url)); response.cookies.delete('session'); return response; } // 处理access token过期,需要刷新的情况 if (session.access_expiration && new Date(session.access_expiration).getTime() <= Date.now()) { try { // 直接调用Django的刷新接口 const refreshRes = await fetch(`${process.env.NEXT_PUBLIC_DJANGO_API}/auth/refresh/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh: session.refresh }), }); if (refreshRes.ok) { const newTokens = await refreshRes.json(); // 更新session内容 const updatedSession = { ...session, access: newTokens.access, access_expiration: newTokens.access_expiration, }; // 设置新的Cookie到响应中 response.cookies.set('session', JSON.stringify(updatedSession), { expires: new Date(session.refresh_expiration || ''), httpOnly: true, path: '/', sameSite: 'strict', }); } else { // 刷新失败,清除Cookie并跳转登录 response = NextResponse.redirect(new URL('/', request.url)); response.cookies.delete('session'); } } catch (err) { console.error('Token刷新失败:', err); response = NextResponse.redirect(new URL('/', request.url)); response.cookies.delete('session'); } } } return response; }
解决思路2:修复API路由的Cookie操作
如果用API路由处理刷新,需要注意:
- 从
NextRequest中获取Cookie,避免环境差异导致的Cookie读取失败; - 通过
NextResponse的setCookie方法更新Cookie,而非依赖Server Action的工具函数。
示例API路由app/api/auth/refresh/route.ts:
import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { const sessionCookie = request.cookies.get('session')?.value; if (!sessionCookie) { return NextResponse.json({ error: '无有效会话' }, { status: 401 }); } const session: UserState = JSON.parse(sessionCookie); try { const { data } = await refreshTokenService(session.refresh || ''); if (data) { const updatedSession = { ...session, access: data.access, access_expiration: data.access_expiration, }; const response = NextResponse.json({ success: true }); response.cookies.set('session', JSON.stringify(updatedSession), { expires: new Date(session.refresh_expiration || ''), httpOnly: true, path: '/', sameSite: 'strict', }); return response; } else { return NextResponse.json({ error: '刷新失败' }, { status: 400 }); } } catch (err) { const response = NextResponse.json({ error: '服务器错误' }, { status: 500 }); response.cookies.delete('session'); return response; } }
前端调用该API时,浏览器会自动保存HttpOnly Cookie,无需手动处理。
解决思路3:Axios拦截器的正确用法
前端Axios拦截器无法直接操作HttpOnly Cookie,正确做法是:当请求返回401(Token过期)时,调用上述API路由或Server Action刷新Token,再重试原请求。
示例Axios拦截器代码:
import axios from 'axios'; const api = axios.create({ baseURL: process.env.NEXT_PUBLIC_DJANGO_API, }); api.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 只处理401且未重试过的请求 if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { // 调用API路由刷新Token await axios.post('/api/auth/refresh'); // 重试原请求 return api(originalRequest); } catch (refreshErr) { // 刷新失败,跳转登录页 window.location.href = '/'; return Promise.reject(refreshErr); } } return Promise.reject(error); } ); export default api;
关键注意事项
- HttpOnly Cookie的安全性:HttpOnly Cookie无法被前端JS读取,所有Cookie操作必须在Server端完成;
- Server Action的调用限制:Server Action只能在React组件的交互事件(如
onClick)或Server Components中调用,Middleware和前端普通JS环境无法直接调用它修改Cookie; - Cookie的路径和SameSite属性:设置Cookie时要确保
path: '/'和合适的sameSite值,避免Cookie无法跨路由生效。
内容的提问来源于stack exchange,提问作者Luc
相关产品推荐
相关产品推荐

