如何在NextJS中对接自定义后端实现认证令牌刷新中间件逻辑
在Next.js中对接自定义后端实现认证令牌刷新机制
问题背景
需要在Next.js中实现基于Cookie的令牌刷新机制,现有思路包括:
- 从Cookie中解码access token和refresh token
- 当access token过期但refresh token有效时,刷新access token
- 将解析后的用户信息存入
currentUserCookie(当前设置无效)
现有代码存在两个核心问题:中间件无法异步调用刷新接口、currentUser Cookie设置不生效。
解决方案
1. 让中间件支持异步操作
Next.js Middleware 完全支持异步函数,只需将 middleware 和校验函数改为 async 类型,即可使用 await 调用后端刷新接口。
2. 完善令牌刷新流程
调用后端刷新接口后,需将新的accessToken(及可能更新的refreshToken)重新写入Cookie,同时更新用户信息。
3. 修复Cookie设置无效问题
req.cookies是只读的请求对象属性,必须通过NextResponse对象设置响应Cookie,同时要指定Cookie的路径、安全属性等(根据你的后端认证配置调整)。
修改后的完整代码
import jwt from 'jsonwebtoken' import { NextRequest, NextResponse } from 'next/server' import authService from './app/lib/auth.service' const protectedRoutes = ['/dashboard'] const publicRoutes = ['/login', '/register', '/'] // 改为async函数支持异步操作 export async function middleware(req: NextRequest) { const path = req.nextUrl.pathname const isProtectedRoute = protectedRoutes.includes(path) const isPublicRoute = publicRoutes.includes(path) // 调用异步校验函数 const { isAuthorized, response } = await checkIfAuthorized(req) // 未授权访问受保护路由:重定向到登录页 if (isProtectedRoute && !isAuthorized) { return NextResponse.redirect(new URL('/login', req.nextUrl)) } // 已授权访问公开路由:重定向到仪表盘 if (isPublicRoute && isAuthorized && path !== '/') { return NextResponse.redirect(new URL('/dashboard', req.nextUrl)) } // 返回修改后的响应(携带新Cookie) return response } // 改为async函数,返回授权状态和处理后的响应 const checkIfAuthorized = async (req: NextRequest): Promise<{ isAuthorized: boolean; response: NextResponse }> => { const response = NextResponse.next() let accessToken = req.cookies.get('accessToken')?.value let decodedAccessToken: any = null // 无accessToken直接返回未授权 if (!accessToken) { return { isAuthorized: false, response } } // 解码并校验accessToken过期时间 decodedAccessToken = jwt.decode(accessToken) as any if (!decodedAccessToken) { return { isAuthorized: false, response } } const accessExpiredAt = new Date(decodedAccessToken.exp * 1000) const now = new Date() // accessToken过期,尝试刷新 if (accessExpiredAt < now) { const refreshToken = req.cookies.get('refreshToken')?.value if (!refreshToken) { return { isAuthorized: false, response } } // 校验refreshToken是否过期 const decodedRefreshToken = jwt.decode(refreshToken) as any const refreshExpiredAt = new Date(decodedRefreshToken.exp * 1000) if (refreshExpiredAt < now) { return { isAuthorized: false, response } } // 调用后端刷新接口,获取新的令牌 try { const refreshResult = await authService.refresh(refreshToken) // 假设后端返回{ accessToken: string, refreshToken?: string } accessToken = refreshResult.accessToken // 更新accessToken Cookie response.cookies.set('accessToken', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', path: '/', maxAge: 15 * 60 // 15分钟,和accessToken有效期一致 }) // 如果后端返回新的refreshToken,也更新 if (refreshResult.refreshToken) { response.cookies.set('refreshToken', refreshResult.refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', path: '/', maxAge: 7 * 24 * 60 * 60 // 7天,和refreshToken有效期一致 }) } // 解码新的accessToken获取用户信息 decodedAccessToken = jwt.decode(accessToken) as any } catch (err) { // 刷新失败,返回未授权 return { isAuthorized: false, response } } } // 设置currentUser Cookie(如果不存在) if (!req.cookies.get('currentUser')?.value && decodedAccessToken) { response.cookies.set('currentUser', JSON.stringify(decodedAccessToken), { secure: process.env.NODE_ENV === 'production', sameSite: 'strict', path: '/', maxAge: 15 * 60 // 和accessToken有效期一致 }) } return { isAuthorized: true, response } } // 中间件忽略静态资源和API路由 export const config = { matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], }
关键说明
- 异步支持:将
middleware和checkIfAuthorized改为async函数,解决了无法调用异步刷新接口的问题。 - Cookie设置:通过
response.cookies.set()设置响应Cookie,指定了httpOnly(仅后端可读取,提升安全性)、secure(生产环境下仅HTTPS传输)、sameSite(防止CSRF)等必要属性。 - 错误处理:刷新令牌时加入try/catch,处理接口调用失败的情况,避免中间件崩溃。
- 响应返回:始终返回处理后的
response对象,确保新设置的Cookie能被客户端接收。
内容的提问来源于stack exchange,提问作者phoebus
相关产品推荐
相关产品推荐

