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

如何在NextJS中实现JWT验证及过期导航自动跳转?

问题原因分析

Next.js 的 Link 组件采用客户端导航(SPA 式路由切换),这种导航方式不会向服务器发送新请求,仅在浏览器端更新页面内容。而你的 Middleware 运行在服务器端,只有当请求到达服务器时才会执行(比如刷新页面、直接输入 URL、外部链接跳转),因此通过 Link 组件导航时,Middleware 不会被触发,自然无法执行过期跳转逻辑。

解决方案

需要在客户端层面添加路由守卫,覆盖客户端导航场景,同时保留原 Middleware 处理服务器端请求的逻辑,实现全场景的过期跳转。

方案1:客户端直接检查JWT(非HttpOnly Cookie场景)

如果你的 JWT 不是 HttpOnly 的 Cookie,可直接在客户端解析并检查过期时间:

1. 编写JWT过期检查函数

// utils/auth.ts
export const isJwtExpired = (token: string | undefined): boolean => {
  if (!token) return true;
  try {
    // 解析JWT payload部分
    const payload = JSON.parse(atob(token.split('.')[1]));
    // 比较过期时间与当前时间(exp是秒级,转成毫秒)
    return payload.exp * 1000 < Date.now();
  } catch (err) {
    // 解析失败视为过期
    return true;
  }
};

2. 添加全局路由守卫组件

创建客户端组件,监听路由变化并检查权限:

// components/AuthGuard.tsx
'use client';

import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { isJwtExpired } from '@/utils/auth';

export default function AuthGuard() {
  const router = useRouter();
  const pathname = usePathname();

  useEffect(() => {
    // 仅在admin路由范围内检查
    if (pathname.startsWith('/admin')) {
      // 从Cookie中获取JWT
      const jwtToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('jwt='))
        ?.split('=')[1];

      if (isJwtExpired(jwtToken)) {
        // 清除过期Cookie并跳转登录页
        document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
        router.push('/login');
      }
    }
  }, [pathname, router]);

  return null;
}

3. 在Admin布局中引入守卫

将守卫组件添加到 Admin 全局布局,确保所有 admin 路由都会触发检查:

// app/admin/layout.tsx
import AuthGuard from '@/components/AuthGuard';

export default function AdminLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <AuthGuard />
      {/* 你的Admin布局内容 */}
      <div className="admin-layout">
        {children}
      </div>
    </>
  );
}

方案2:通过API检查JWT(HttpOnly Cookie场景)

如果你的 JWT 是 HttpOnly 的 Cookie(更安全的做法),客户端无法直接读取,需通过后端 API 验证:

1. 创建权限检查API

// app/api/check-auth/route.ts
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';

export async function GET(request: NextRequest) {
  // 从HttpOnly Cookie中获取JWT
  const jwtToken = request.cookies.get('jwt')?.value;
  
  if (!jwtToken) {
    return NextResponse.json({ authenticated: false }, { status: 401 });
  }

  try {
    // 验证JWT有效性
    jwt.verify(jwtToken, process.env.JWT_SECRET!);
    return NextResponse.json({ authenticated: true }, { status: 200 });
  } catch (err) {
    // 验证失败(过期/无效)
    return NextResponse.json({ authenticated: false }, { status: 401 });
  }
}

2. 修改路由守卫的检查逻辑

// components/AuthGuard.tsx
'use client';

import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';

export default function AuthGuard() {
  const router = useRouter();
  const pathname = usePathname();

  useEffect(() => {
    const checkAuthStatus = async () => {
      if (pathname.startsWith('/admin')) {
        try {
          const res = await fetch('/api/check-auth', { credentials: 'include' });
          if (!res.ok) {
            // 权限验证失败,跳转登录页
            router.push('/login');
          }
        } catch (err) {
          // 请求失败也视为无权限
          router.push('/login');
        }
      }
    };

    checkAuthStatus();
  }, [pathname, router]);

  return null;
}

不推荐方案:强制服务器端导航

如果你愿意牺牲客户端导航的流畅性,可以用普通的 <a> 标签代替 Link 组件,这样每次点击都会发送服务器请求触发 Middleware。但这种方式会失去 SPA 路由的优势,不建议在后台管理系统中使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 11:35:02