NextJS使用中间件获取刷新令牌遇页面认证状态异常求助
解决方案
方案1:用中间件异步刷新Token并通过Rewrite传递
Next.js中间件支持异步操作,你可以在中间件里完成Token刷新,然后用NextResponse.rewrite重写当前请求——这个方法不会修改浏览器历史记录,同时能让后续的服务端页面拿到更新后的Token。
具体实现步骤:
- 在中间件中检测Access Token是否过期
- 若过期且存在Refresh Token,异步调用刷新接口获取新Token
- 将新Token写入Cookie,用
rewrite返回当前请求的响应
代码示例:
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export async function middleware(request: NextRequest) { const accessToken = request.cookies.get('accessToken')?.value const refreshToken = request.cookies.get('refreshToken')?.value // 校验Token是否过期(替换成你自己的校验逻辑) const isTokenExpired = accessToken ? checkTokenExpiry(accessToken) : true if (isTokenExpired && refreshToken) { try { // 调用你的刷新接口 const refreshRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }), }) if (refreshRes.ok) { const { newAccessToken, newRefreshToken } = await refreshRes.json() // 构造新响应,更新Cookie const updatedResponse = NextResponse.rewrite(request.url) updatedResponse.cookies.set('accessToken', newAccessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 15 * 60, // 匹配你的Access Token有效期 }) updatedResponse.cookies.set('refreshToken', newRefreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60, // 匹配你的Refresh Token有效期 }) return updatedResponse } } catch (err) { // 刷新失败,按需跳转登录页 return NextResponse.redirect(new URL('/login', request.url)) } } // Token有效或无Refresh Token,继续处理请求 return NextResponse.next() } // 辅助函数:解析Token并检查过期时间 function checkTokenExpiry(token: string): boolean { try { const payload = JSON.parse(atob(token.split('.')[1])) return payload.exp * 1000 < Date.now() } catch { return true // 解析失败视为过期 } }
方案2:服务端组件内前置Token刷新逻辑
如果你的页面是服务端组件,可以在组件渲染前先完成Token刷新,确保后续数据请求用的是有效Token。这种方式不需要依赖中间件,直接在组件内处理:
代码示例:
import { cookies, redirect } from 'next/headers' async function ProtectedServerPage() { const cookieStore = cookies() let accessToken = cookieStore.get('accessToken')?.value const refreshToken = cookieStore.get('refreshToken')?.value // 检查并刷新Token if (!accessToken || checkTokenExpiry(accessToken)) { if (!refreshToken) redirect('/login') const refreshRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }), }) if (!refreshRes.ok) redirect('/login') const { newAccessToken, newRefreshToken } = await refreshRes.json() // 更新Cookie cookies().set('accessToken', newAccessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 15 * 60, }) cookies().set('refreshToken', newRefreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60, }) accessToken = newAccessToken } // 用新Token请求数据 const dataRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/protected-data`, { headers: { Authorization: `Bearer ${accessToken}` }, }) const content = await dataRes.json() return <div>{content.data}</div> } // 复用之前的校验函数 function checkTokenExpiry(token: string): boolean { try { const payload = JSON.parse(atob(token.split('.')[1])) return payload.exp * 1000 < Date.now() } catch { return true } } export default ProtectedServerPage
关键注意事项
- 刷新接口要做好安全防护,比如校验请求来源、添加CSRF Token,避免恶意刷新
- 中间件的异步操作建议添加超时控制,避免请求长时间挂起
- 如果是客户端组件,可在
useEffect中处理Token刷新,但服务端渲染场景优先用上述两种方案
内容的提问来源于stack exchange,提问作者Trace
相关产品推荐
相关产品推荐

